﻿Imports System.ComponentModel

''' <summary>
''' This class created as base class to display time series data in properties grid; there is one series instance per curve
''' </summary>
<DefaultProperty("Label")> _
Friend MustInherit Class clsSeries

    Protected _SeriesType As enumGraphType
    Protected _Zed As ZedGraph.ZedGraphControl
    Protected _ZedText As New Generic.List(Of ZedGraph.TextObj)
    Protected _NeedsReload As Boolean 'set to true if one of the variables that controls what data to acquire change (in the inherited class)
    Protected TimeWindowMin As Double() = {0.05, 1, 5, 15, 30, 60, 120, 360, 720, 1440, 4320, 10080, 20160, 43200, 129600, 259200, 525600, 52000000, 0}
    Friend AutoLabel As Boolean = True

    ''' <summary>
    ''' Class that contains additional selection criteria
    ''' </summary>
    Friend Property MoreCriteria As New clsMoreCriteria

    Friend Enum enumLineType
        None = -1
        Solid
        Dash
        Dot
        DashDot
        DashDotDot
        Bars
    End Enum

    Friend Enum enumSymbolType
        None = -1
        Circle
        Square
        Diamond
        Triangle
        Tri_Down
        Star
        XCross
        Plus
        HDash
        VDash
        Box_Whisker
    End Enum

    Protected Structure structRCode
        Dim RCode As String
        Dim Calc_Stats As Boolean
        Dim NonDetect_PCode As Boolean
        Dim NonDetect_Result As Boolean
        Dim Detect_Mult As Double
    End Structure

    ''' <summary>
    ''' List of available X axes
    ''' </summary>
    Friend Enum enumXAxis
        ''' <summary>X Axis plotted on bottom</summary>
        Bottom
        ''' <summary>X Axis plotted on top</summary>
        Top
    End Enum

    ''' <summary>
    ''' List of available Y axes
    ''' </summary>
    Friend Enum enumYAxis
        ''' <summary>Y Axis plotted on left</summary>
        Left
        ''' <summary>Y Axis plotted on right</summary>
        Right
    End Enum

    ''' <summary>
    ''' List of available Y axis indices
    ''' </summary>
    Friend Enum enumYAxisIndex
        Axis_1
        Axis_2
        Axis_3
        Axis_4
        Axis_5
    End Enum

    ''' <summary>
    ''' Construct new series instance and set default line width and symbol
    ''' </summary>
    ''' <param name="Zed">Existing ZedGraph control</param>
    Friend Sub New(ByRef Zed As ZedGraph.ZedGraphControl, ByVal SeriesType As enumGraphType)
        _Zed = Zed
        _SeriesType = SeriesType
        ZedCurve = New ZedGraph.LineItem("")
        ZedCurveMeas = New ZedGraph.LineItem("")
        _Zed.GraphPane.CurveList.Insert(0, ZedCurve)
        TimeWindow = ActiveGraphTab.AnimationInfo.TimeWindow
        _SymbolType = enumSymbolType.Circle
        _LineType = enumLineType.Solid
        _StatsFont = New Font("Tahoma", 8)
        Reset()
    End Sub

    Friend Function Clone() As clsSeries
        Return MemberwiseClone()
    End Function

    ''' <summary>
    ''' ZedCurve item containing the main data for the series
    ''' </summary>
    Friend Property ZedCurve() As ZedGraph.LineItem

    ''' <summary>
    ''' ZedCurve item containing the measured data for the calibration series
    ''' </summary>
    Friend Property ZedCurveMeas() As ZedGraph.LineItem

    ''' <summary>
    ''' Add ZedCurve to curvelist in graph pane (for calibration, plot measured symbols on top of simulated lines)
    ''' </summary>
    ''' <remarks></remarks>
    Friend Sub AddZedCurve()
        _Zed.GraphPane.CurveList.Add(ZedCurve)
        If Table IsNot Nothing AndAlso Table.TableType = clsTable.enumTableType.Calibration Then
            If Table.Calibration.SeriesOrder = clsCalibration.enumSeriesOrder.MeasOverSim Then 'append
                _Zed.GraphPane.CurveList.Add(ZedCurveMeas)
            Else 'insert before so it is beneath it
                _Zed.GraphPane.CurveList.Insert(_Zed.GraphPane.CurveList.IndexOf(ZedCurve), ZedCurveMeas)
            End If
        End If
    End Sub

    ''' <summary>
    ''' Remove ZedCurve from curvelist in graph pane
    ''' </summary>
    ''' <remarks></remarks>
    Friend Sub RemoveZedCurve()
        _Zed.GraphPane.CurveList.Remove(ZedCurve)
        If Table IsNot Nothing AndAlso Table.TableType = clsTable.enumTableType.Calibration Then
            _Zed.GraphPane.CurveList.Remove(ZedCurveMeas)
        End If
    End Sub

    ''' <summary>
    ''' Label associated with series
    ''' </summary>
    <DisplayName("1. Series Label"), Category("(Data)"), Description("Sets the label associated with this series")> _
    Public Overridable Property Label() As String
        Get
            Return ZedCurve.Label.Text
        End Get
        Set(ByVal value As String)
            If value = "" Then
                GetDefaultLabel()
                AutoLabel = True
            Else
                AutoLabel = False
            End If
            ZedCurve.Label.Text = value
        End Set
    End Property

    <DisplayName("2. Is Visible?"), Category("(Data)"), Description("Is Series visible?")> _
    Public Property Visible() As Boolean
        Get
            Return ZedCurve.IsVisible
        End Get
        Set(ByVal value As Boolean)
            ZedCurve.IsVisible = value
            ZedCurve.Label.IsVisible = value
            ZedCurveMeas.IsVisible = value And ZedCurveMeas.Symbol.Type <> ZedGraph.SymbolType.None
            ZedCurveMeas.Label.IsVisible = value And ZedCurveMeas.Symbol.Type <> ZedGraph.SymbolType.None
        End Set
    End Property

    ''' <summary>
    ''' Is this a calced series? If true, the data points are only stored in the ZedCurve and the Table is Nothing
    ''' </summary>
    <DisplayName("3. Is Calculated?"), Category("(Data)"), Description("Indicates if this is a calculated series (read-only)"), [ReadOnly](True)> _
    Public Property IsCalc() As Boolean

    <DisplayName("4. Table Name"), Category("(Data)"), Description("Table name associated with series (read-only)"), [ReadOnly](True)> _
    Public Overridable Property Table() As clsTable

    Protected _StationID As String 'for longitudinal profile contains branch ID or "All Branches"

    ''' <summary>
    ''' Station ID associated with series (for longitudinal profiles, this is the Branch Name to plot)
    ''' </summary>
    <DisplayName("5. Station ID"), Category("(Data)"), Description("Station ID associated with series (read-only)"), [ReadOnly](True)> _
    Public Property StationID() As String
        Get
            Return _StationID
        End Get
        Set(ByVal value As String)
            StatsVisible = False
            _StationID = value
            _NeedsReload = True
        End Set
    End Property

    Protected _PCode As String

    ''' <summary>
    ''' PCode associated with series
    ''' </summary>
    <DisplayName("6. PCode"), Category("(Data)"), Description("PCode associated with series (read-only)"), [ReadOnly](True)> _
    Public Property PCode() As String
        Get
            Return _PCode
        End Get
        Set(ByVal value As String)
            StatsVisible = False
            _PCode = value
            _NeedsReload = True
        End Set
    End Property

    ''' <summary>
    ''' PCode associated with series
    ''' </summary>
    <DisplayName("7. Other Criteria"), Category("(Data)"), Description("Additional selection criteria for series (read-only)"), [ReadOnly](True)> _
    Public ReadOnly Property MoreCriteriaText() As String
        Get
            If MoreCriteria Is Nothing Then Return "None" Else Return MoreCriteria.ToString
        End Get
    End Property

    Protected _DateTime As Date 'used for profile plots only

    ''' <summary>
    ''' Date-time associated with series
    ''' </summary>
    <DisplayName("8. Date-Time"), Category("(Data)"), Description("Date-time value associated with profile series (read-only)"), [ReadOnly](True)> _
    Public Property DateTime() As String
        Get
            Return _DateTime.ToString(MyDateTimeFormat)
        End Get
        Set(ByVal value As String)
            If IsDate(value) Then
                _DateTime = CDate(value)
                _NeedsReload = True
            End If
        End Set
    End Property

    Protected _TimeWindow As enumTimeWindow

    ''' <summary>
    ''' Time window associated with series
    ''' </summary>
    <DisplayName("9. Time Window"), Category("(Data)"), Description("Time window associated with profile series (read-only)"), [ReadOnly](True)> _
    Public Property TimeWindow() As enumTimeWindow
        Get
            Return _TimeWindow
        End Get
        Set(ByVal value As enumTimeWindow)
            _TimeWindow = value
            _NeedsReload = True
        End Set
    End Property

    ''' <summary>
    ''' List of valid Station IDs associated with series (semi-colon separated) (doesn't apply to long. profiles)
    ''' </summary>
    Friend Property StationList() As String

    ''' <summary>
    ''' List of valid PCodes associated with series (semi-colon separated)
    ''' </summary>
    Friend Property PCodeList() As String

    ''' <summary>
    ''' Dictionary of Station ID/Segment Name (key) and river mile (value); only used for long. profiles
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks>Normally just place branch ID into StationID (or "All Branches"), if select segments using GIS, set StationID to empty string and populate this dictionary instead</remarks>
    Friend Property dictProfile As Dictionary(Of String, Double)

    ''' <summary>
    ''' Given new StationID, find the unique PCodes that match so can update PCodeList
    ''' </summary>
    Friend Function GetPCodeList(StationID As String) As String
        Try
            If IsCalc OrElse
                Table.TableType = clsTable.enumTableType.BMD OrElse
                Table.TableType = clsTable.enumTableType.BMD2 OrElse
                Table.TableType = clsTable.enumTableType.WRDBData OrElse
                Table.TableType = clsTable.enumTableType.Calibration Then Return PCodeList
            Application.UseWaitCursor = True
            Application.DoEvents()
            Dim PCodes As String = ""
            Dim dr As DbDataReader = Table.WRDBProject.DB.ExecuteReader("SELECT DISTINCT Dta.PCode FROM {0} Dta WHERE Dta.Station_ID='{1}'{2}", Table.WRDBProject.DB.AddBrackets(Table.TableName), StationID, Table.SelectedByClause)
            While dr IsNot Nothing AndAlso dr.Read
                If Not dr.IsDBNull(0) Then PCodes &= IIf(PCodes = "", "", ";") & dr.GetString(0)
            End While
            dr.Close()
            Return PCodes
        Catch ex As Exception
            ErrorMsg(, ex)
            Return ""
        Finally
            Application.UseWaitCursor = False
        End Try
    End Function

    ''' <summary>
    ''' List of valid dates associated with series (semi-colon separated) (applies only to profiles)
    ''' </summary>
    Friend Property DateList() As String

    ''' <summary>
    ''' Given StationID and PCode, find the unique dates so can update DateList
    ''' </summary>
    Friend Function GetDateList(ByVal StationID As String, ByVal PCode As String) As String
        Try
            If IsCalc OrElse
                Table.TableType = clsTable.enumTableType.BMD OrElse
                Table.TableType = clsTable.enumTableType.BMD2 OrElse
                Table.TableType = clsTable.enumTableType.WRDBData OrElse
                Table.TableType = clsTable.enumTableType.Calibration Then Return DateList
            Application.UseWaitCursor = True
            Application.DoEvents()
            Dim Dates As String = ""
            Dim dr As DbDataReader = Table.WRDBProject.DB.ExecuteReader("SELECT DISTINCT Dta.Date_Time FROM {0} Dta WHERE Dta.Station_ID='{1}' AND Dta.PCode='{2}'{3}", Table.WRDBProject.DB.AddBrackets(Table.TableName), StationID, PCode, Table.SelectedByClause)
            While dr IsNot Nothing AndAlso dr.Read
                If Not dr.IsDBNull(0) Then Dates &= IIf(Dates = "", "", ";") & CDate(dr.GetValue(0)).ToString(MyDateTimeFormat)
            End While
            dr.Close()
            Return Dates
        Catch ex As Exception
            ErrorMsg(, ex)
            Return ""
        Finally
            Application.UseWaitCursor = False
        End Try
    End Function

    ''' <summary>
    ''' When forming new series, call this to set the default series name
    ''' </summary>
    Friend Overridable Sub GetDefaultLabel()
        'don't want to set Label property because that will reset AutoLabel property
        Label = String.Format("{0} at {1}", _PCode, _StationID)
        AutoLabel = True
    End Sub

    Friend Overridable Sub GetData()
        Try
            'reset counters
            Table.NumNonCalc = 0
            Table.NumNonDetect = 0
            Table.NumExceed = 0

            For Each obj As ZedGraph.TextObj In _ZedText
                _Zed.GraphPane.GraphObjList.Remove(obj)
                obj = Nothing
            Next
            _ZedText.Clear()

        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

    Protected Sub GetMinMaxDate(ByRef MinDate As Date, ByRef MaxDate As Date)
        If TimeWindow = enumTimeWindow.BMD Then 'set window based on 1/2 of time difference to prior and next steps in first BMD file encountered
            For Each tbl As clsTable In OpenTables
                Select Case tbl.TableType
                    Case clsTable.enumTableType.BMD
                        With tbl.dsBMD
                            Dim idx As Integer = .TimeIndex(_DateTime)
                            If idx = 0 Then
                                Dim dt As Double = .Time(1).Subtract(.Time(0)).TotalMinutes / 2
                                MinDate = _DateTime.AddMinutes(-dt)
                                MaxDate = _DateTime.AddMinutes(dt)
                            ElseIf idx = tbl.dsBMD.NumTimes - 1 Then
                                Dim dt As Double = .Time(.NumTimes - 1).Subtract(.Time(.NumTimes - 2)).TotalMinutes / 2
                                MinDate = _DateTime.AddMinutes(-dt)
                                MaxDate = _DateTime.AddMinutes(dt)
                            Else
                                Dim index As Integer = .TimeIndex(_DateTime)
                                Dim dt1 As Double = .Time(index).Subtract(.Time(index - 1)).TotalMinutes / 2
                                Dim dt2 As Double = .Time(index + 1).Subtract(.Time(index)).TotalMinutes / 2
                                MinDate = _DateTime.AddMinutes(-dt1)
                                MaxDate = _DateTime.AddMinutes(dt2)
                            End If
                        End With
                        Exit For
                    Case clsTable.enumTableType.BMD2
                        With tbl.dsBMD2
                            .Reopen()
                            Dim idx As Integer = .MomentIndex(_DateTime)
                            If idx = 0 Then
                                Dim dt As Double = .NthMoment(1).Subtract(.NthMoment(0)).TotalMinutes / 2
                                MinDate = _DateTime.AddMinutes(-dt)
                                MaxDate = _DateTime.AddMinutes(dt)
                            ElseIf idx = .NumMoments - 1 Then
                                Dim dt As Double = .NthMoment(.NumMoments - 1).Subtract(.NthMoment(.NumMoments - 2)).TotalMinutes / 2
                                MinDate = _DateTime.AddMinutes(-dt)
                                MaxDate = _DateTime.AddMinutes(dt)
                            Else
                                Dim index As Integer = .MomentIndex(_DateTime)
                                Dim dt1 As Double = .NthMoment(index).Subtract(.NthMoment(index - 1)).TotalMinutes / 2
                                Dim dt2 As Double = .NthMoment(index + 1).Subtract(.NthMoment(index)).TotalMinutes / 2
                                MinDate = _DateTime.AddMinutes(-dt1)
                                MaxDate = _DateTime.AddMinutes(dt2)
                            End If
                            .Close()
                        End With
                        Exit For
                End Select
            Next
        Else
            MinDate = _DateTime.AddMinutes(-TimeWindowMin(TimeWindow) / 2)
            MaxDate = _DateTime.AddMinutes(TimeWindowMin(TimeWindow) / 2)
        End If
    End Sub

    ''' <summary>
    ''' Replace the table associated with this series; if the new table doesn't have the previous StationID/PCode available, they will be reset
    ''' </summary>
    ''' <param name="NewTable">New table to assign</param>
    ''' <remarks></remarks>
    Friend Sub ReplaceTable(ByVal NewTable As clsTable)
        Table = NewTable
        Dim lst As Generic.List(Of String)
        If TypeOf Me Is clsSeriesTimeSeries Then
            lst = Table.GetStationList("All Branches")
        Else
            lst = Table.GetBranchList()
        End If

        Dim StationList As String = ""
        For Each s As String In lst
            StationList &= IIf(StationList = "", "", ";") & StringSplit(s, " - ")(0)
        Next
        lst.Clear()
        lst.AddRange(StationList.Split(";"))
        If Not lst.Contains(StationID) Then StationID = StringSplit(lst(0), " - ")(0)
        StationList = StationList

        lst = Table.GetPCodeList(StationID)
        PCodeList = ListToString(lst)
        If Not lst.Contains(PCode) AndAlso lst.Count > 0 Then PCode = lst(0)

        If TypeOf Me Is clsSeriesScatter Then
            With CType(Me, clsSeriesScatter)
                lst = .Table.GetPCodeList(.StationID)
                .PCodeListX = ListToString(lst)
                If Not lst.Contains(.PCodeX) AndAlso lst.Count > 0 Then .PCodeX = lst(0)
            End With
        End If

        Select Case _SeriesType
            Case enumGraphType.Depth, enumGraphType.Profile, enumGraphType.Scatter, enumGraphType.Width
                DateList = GetDateList(StationID, PCode)
            Case Else
                DateList = ""
        End Select

        GetData()
    End Sub

    Friend Overridable Sub Reset()
        XAxis = enumXAxis.Bottom
        YAxis = enumYAxis.Left
        YAxisIndex = enumYAxisIndex.Axis_1
        SmoothCurve = False
        LineType = Drawing2D.DashStyle.Solid
        LineWidth = 1
        SymbolType = enumSymbolType.None
        SymbolSize = 4
        SymbolFill = False
    End Sub

    ''' <summary>
    ''' Number of data points in series
    ''' </summary>
    <DisplayName("1. Number of Points"), Category("Statistics"), Description("Number of data points in series (read-only)"), [ReadOnly](True)> _
    Public ReadOnly Property NumberOfPoints() As Integer
        Get
            Return ZedCurve.Points.Count
        End Get
    End Property

    ''' <summary>
    ''' Number of non-calc data points in series
    ''' </summary>
    <DisplayName("2. No. Non-Calc"), Category("Statistics"), Description("Number of data points in series with RCode indicating that it shouldn't be used in calculations (read-only)"), [ReadOnly](True)> _
    Public ReadOnly Property NumberOfNonCalc() As Integer
        Get
            If Table Is Nothing Then Return 0 Else Return Table.NumNonCalc
        End Get
    End Property

    ''' <summary>
    ''' Number of non-detect data points in series
    ''' </summary>
    <DisplayName("3. No. Non-Detect"), Category("Statistics"), Description("Number of non-detect data points in series (read-only)"), [ReadOnly](True)> _
    Public ReadOnly Property NumberOfNonDetect() As Integer
        Get
            If Table Is Nothing Then Return 0 Else Return Table.NumNonDetect
        End Get
    End Property

    ''' <summary>
    ''' Number of data points in series outside of criteria limits
    ''' </summary>
    <DisplayName("4. No. Exceedances"), Category("Statistics"), Description("Number of data points in series exceeding criteria limits for PCode (read-only)"), [ReadOnly](True)> _
    Public ReadOnly Property NumberOfExceedances() As Integer
        Get
            If Table Is Nothing Then Return 0 Else Return Table.NumExceed
        End Get
    End Property

    Protected _StatsText As String = ""

    ''' <summary>
    ''' Arbitrary notes on graph
    ''' </summary>
    <DisplayName("1. Statistics Text"), Category("Statistics Text"), Description("Text containing statistics associated with series (automatically updated)"), Editor("System.ComponentModel.Design.MultilineStringEditor", "System.Drawing.Design.UITypeEditor")> _
    Public Property StatsText() As String
        Get
            Return _StatsText
        End Get
        Set(ByVal value As String)
            _StatsText = value
            If _StatsText <> "" And _StatsVisible Then
                _StatsPosition = enumNotePosition.TopRight
                _StatsVisible = True
            End If
            SetStats()
        End Set
    End Property

    Protected _StatsFont As Font

    ''' <summary>
    ''' Graph title font (Zed.GraphPane.Title.FontSpec)
    ''' </summary>
    <DisplayName("2. Stats font"), Category("Statistics Text"), Description("Sets the font for the statistics")> _
    Public Property StatsFont() As Font
        Get
            Return _StatsFont
        End Get
        Set(ByVal value As Font)
            _StatsFont = value
            SetStats()
        End Set
    End Property

    Protected _StatsVisible As Boolean = False

    ''' <summary>
    ''' Are notes visible? 
    ''' </summary>
    <DisplayName("3. Stats are visible?"), Category("Statistics Text"), Description("Sets the visibility of statistics text")> _
    Public Property StatsVisible() As Boolean
        Get
            Return _StatsVisible
        End Get
        Set(ByVal value As Boolean)
            _StatsVisible = value
            SetStats()
        End Set
    End Property

    Protected _StatsPosition As enumNotePosition = enumNotePosition.TopRight

    ''' <summary>
    ''' Note position
    ''' </summary>
    <DisplayName("4. Stats position"), Category("Statistics Text"), Description("Sets the position of the statistics text if used")> _
    Public Property StatsPosition() As enumNotePosition
        Get
            Return _StatsPosition
        End Get
        Set(ByVal value As enumNotePosition)
            _StatsPosition = value
            SetStats()
        End Set
    End Property

    Protected _StatsAlignment As enumNoteAlignment = enumNoteAlignment.Center

    ''' <summary>
    ''' Note text alignment
    ''' </summary>
    <DisplayName("5. Statistics text alignment"), Category("Statistics Text"), Description("Sets the alignment of the text contained in the statistics text if used")> _
    Public Property StatsAlignment() As enumNoteAlignment
        Get
            Return _StatsAlignment
        End Get
        Set(ByVal value As enumNoteAlignment)
            _StatsAlignment = value
            SetStats()
        End Set
    End Property

    Protected _StatsBorder As Boolean = True

    ''' <summary>
    ''' Note border
    ''' </summary>
    <DisplayName("6. Stats border is visible?"), Category("Statistics Text"), Description("If true, a solid border will be drawn around the statistics text (and the background will be filled).")> _
    Public Property StatsBorder() As Boolean
        Get
            Return _StatsBorder
        End Get
        Set(ByVal value As Boolean)
            _StatsBorder = value
            SetStats()
        End Set
    End Property

    Private Sub SetStats()
        'remove any prior object
        For Each obj As ZedGraph.GraphObj In _Zed.GraphPane.GraphObjList
            If TypeOf obj Is ZedGraph.TextObj AndAlso obj.Tag = "Stats:" & _StationID & _PCode & IIf(IsCalc, "Calc", "") Then
                _Zed.GraphPane.GraphObjList.Remove(obj)
                Exit For
            End If
        Next

        If Not _StatsVisible Then Exit Sub

        'if non empty, add a new one
        If _StatsText <> "" And _StatsVisible Then
            Dim obj As New ZedGraph.TextObj()
            With obj
                .Text = _StatsText.Replace("\n", vbCrLf)
                .Tag = "Stats:" & _StationID & _PCode & IIf(IsCalc, "Calc", "")
                .FontSpec = GetFont(_StatsFont, .FontSpec, ActiveGraphTab.GraphOptions.FontScaleFactor)
                .FontSpec.StringAlignment = Choose(_StatsAlignment + 1, StringAlignment.Near, StringAlignment.Center, StringAlignment.Far)
                If _StatsBorder Then
                    .FontSpec.Fill = New ZedGraph.Fill(Color.White)
                    .FontSpec.Border.IsVisible = True
                Else
                    .FontSpec.Fill = New ZedGraph.Fill(Color.Transparent)
                    .FontSpec.Border.IsVisible = False
                End If
                Select Case _StatsPosition
                    Case enumNotePosition.TopLeft
                        .Location = New ZedGraph.Location(0.02, 0.02, ZedGraph.CoordType.ChartFraction, ZedGraph.AlignH.Left, ZedGraph.AlignV.Top)
                    Case enumNotePosition.TopCenter
                        .Location = New ZedGraph.Location(0.5, 0.02, ZedGraph.CoordType.ChartFraction, ZedGraph.AlignH.Center, ZedGraph.AlignV.Top)
                    Case enumNotePosition.TopRight
                        .Location = New ZedGraph.Location(0.98, 0.02, ZedGraph.CoordType.ChartFraction, ZedGraph.AlignH.Right, ZedGraph.AlignV.Top)
                    Case enumNotePosition.BottomLeft
                        .Location = New ZedGraph.Location(0.02, 0.98, ZedGraph.CoordType.ChartFraction, ZedGraph.AlignH.Left, ZedGraph.AlignV.Bottom)
                    Case enumNotePosition.BottomCenter
                        .Location = New ZedGraph.Location(0.5, 0.98, ZedGraph.CoordType.ChartFraction, ZedGraph.AlignH.Center, ZedGraph.AlignV.Bottom)
                    Case enumNotePosition.BottomRight
                        .Location = New ZedGraph.Location(0.98, 0.98, ZedGraph.CoordType.ChartFraction, ZedGraph.AlignH.Right, ZedGraph.AlignV.Bottom)
                End Select
            End With
            _Zed.GraphPane.GraphObjList.Add(obj)
            _Zed.Refresh()
        End If
    End Sub

    ''' <summary>
    ''' X-Axis to which this series is assigned
    ''' </summary>
    <DisplayName("1. X Axis"), Category("Axes"), Description("Sets X-Axis to which this series is assigned")> _
    Public Property XAxis() As enumXAxis
        Get
            Return IIf(ZedCurve.IsX2Axis, enumXAxis.Top, enumXAxis.Bottom)
        End Get
        Set(ByVal value As enumXAxis)
            ZedCurve.IsX2Axis = value = enumXAxis.Top
            ZedCurveMeas.IsX2Axis = ZedCurve.IsX2Axis
        End Set
    End Property

    ''' <summary>
    ''' Y-Axis to which this series is assigned
    ''' </summary>
    <DisplayName("2. Y Axis"), Category("Axes"), Description("Sets Y-Axis to which this series is assigned")> _
    Public Property YAxis() As enumYAxis
        Get
            Return IIf(ZedCurve.IsY2Axis, enumYAxis.Right, enumYAxis.Left)
        End Get
        Set(ByVal value As enumYAxis)
            ZedCurve.IsY2Axis = value = enumYAxis.Right
            ZedCurve.YAxisIndex = 0
            ZedCurveMeas.IsY2Axis = value
            ZedCurveMeas.YAxisIndex = 0
        End Set
    End Property

    ''' <summary>
    ''' Y-Axis index to which this series is assigned
    ''' </summary>
    <DisplayName("3. Y Axis No."), Category("Axes"), Description("Sets Y-Axis number to which this series is assigned (from 1 to 5); you can have multiple axes on the left and/or right Y axis")> _
    Public Property YAxisIndex() As enumYAxisIndex
        Get
            Return ZedCurve.YAxisIndex
        End Get
        Set(ByVal value As enumYAxisIndex)
            ZedCurve.YAxisIndex = value
            ZedCurveMeas.YAxisIndex = value
        End Set
    End Property

    'following not used because would need setting for both calibration types and would be too messy

    ' ''' <summary>
    ' ''' Set legend text for this series
    ' ''' </summary>
    '<DisplayName("1. Legend Text"), Category("Legend Entry"), Description("Sets the text for the legend entry for this series")> _
    'Public Property LegendText() As String
    '    Get
    '        Return ZedCurve.Label.Text
    '    End Get
    '    Set(ByVal value As String)
    '        ZedCurve.Label.Text = value
    '    End Set
    'End Property

    ' ''' <summary>
    ' ''' Set legend text for this series
    ' ''' </summary>
    '<DisplayName("2. Is Visible?"), Category("Legend Entry"), Description("Indicates whether the legend entry is visible")> _
    'Public Property LegendVisible() As Boolean
    '    Get
    '        Return ZedCurve.Label.IsVisible
    '    End Get
    '    Set(ByVal value As Boolean)
    '        ZedCurve.Label.IsVisible = value
    '        _Zed.Refresh()
    '    End Set
    'End Property

    ''' <summary>
    ''' Set color for this series
    ''' </summary>
    <DisplayName("1. Line Color"), Category("Line & Symbol Format"), Description("Sets the color of lines")> _
    Public Property LineColor() As Color
        Get
            Return ZedCurve.Line.Color
        End Get
        Set(ByVal value As Color)
            If SymbolFillColor = LineColor And SymbolBorderColor = LineColor Then 'reset all to new value--reseting curve color does this automatically
                ZedCurve.Color = value
                ZedCurveMeas.Color = value
            Else 'symbol colors had been overridden, so leave as-is (save and reset)
                'Dim fc As Color = SymbolFillColor
                'Dim bc As Color = SymbolBorderColor
                ZedCurve.Line.Color = value
                ZedCurveMeas.Line.Color = value
                'SymbolFillColor = fc
                'SymbolBorderColor = bc
            End If
        End Set
    End Property

    Protected _LineType As enumLineType

    ''' <summary>
    ''' Set line type for this series (line is not visible if -1)
    ''' </summary>
    <DisplayName("2. Line Type"), Category("Line & Symbol Format"), Description("Sets the line type")> _
    Public Overridable Property LineType() As enumLineType
        Get
            Return _LineType
        End Get
        Set(ByVal value As enumLineType)
            Dim WasBars As Boolean = _LineType = enumLineType.Bars
            _LineType = value
            With ZedCurve
                If value = enumLineType.None Then
                    .Line.IsVisible = False
                ElseIf value = enumLineType.Bars Then
                    .Line.Style = Drawing2D.DashStyle.Solid
                    .Line.IsVisible = True
                Else
                    .Line.Style = value
                    .Line.IsVisible = True
                End If
                .IsVisible = .Line.IsVisible Or .Symbol.IsVisible Or SymbolType = enumSymbolType.Box_Whisker
                .Label.IsVisible = .IsVisible
            End With
            ZedCurveMeas.Line.IsVisible = False
            If _SymbolType = enumSymbolType.Box_Whisker OrElse _LineType = enumLineType.Bars OrElse WasBars Then GetData()
        End Set
    End Property

    ''' <summary>
    ''' Sets the line width (in points)
    ''' </summary>
    <DisplayName("3. Line Thickness"), Category("Line & Symbol Format"), Description("Sets the line width (in points)")> _
    Public Property LineWidth() As Single
        Get
            Return ZedCurve.Line.Width
        End Get
        Set(ByVal value As Single)
            ZedCurve.Line.Width = value
        End Set
    End Property

    ''' <summary>
    ''' Is a curve fit through the data points?
    ''' </summary>
    <DisplayName("4. Smooth Curve?"), Category("Line & Symbol Format"), Description("Is a curve fit through the data points?")> _
    Public Property SmoothCurve() As Boolean
        Get
            Return ZedCurve.Line.IsSmooth
        End Get
        Set(ByVal value As Boolean)
            With ZedCurve.Line
                If _SymbolType = enumSymbolType.Box_Whisker Then
                    .IsSmooth = False
                Else
                    .IsSmooth = value
                End If
            End With
        End Set
    End Property

    Protected _SymbolType As enumSymbolType

    ''' <summary>
    ''' Sets the symbol style
    ''' </summary>
    <DisplayName("5. Symbol Type"), Category("Line & Symbol Format"), Description("Sets the symbol style")> _
    Public Overridable Property SymbolType() As enumSymbolType
        Get
            Return _SymbolType
        End Get
        Set(ByVal value As enumSymbolType)
            Dim WasBW As Boolean = _SymbolType = enumSymbolType.Box_Whisker
            _SymbolType = value
            If ActiveGraphTab.GraphType <> enumGraphType.Profile And _SymbolType = enumSymbolType.Box_Whisker Then _SymbolType = enumSymbolType.Square 'only profile plots can show box/whisker

            With ZedCurve
                Select Case _SymbolType
                    Case enumSymbolType.None : .Symbol.Type = ZedGraph.SymbolType.None
                    Case enumSymbolType.Square : .Symbol.Type = ZedGraph.SymbolType.Square
                    Case enumSymbolType.Diamond : .Symbol.Type = ZedGraph.SymbolType.Diamond
                    Case enumSymbolType.Triangle : .Symbol.Type = ZedGraph.SymbolType.Triangle
                    Case enumSymbolType.Circle : .Symbol.Type = ZedGraph.SymbolType.Circle
                    Case enumSymbolType.XCross : .Symbol.Type = ZedGraph.SymbolType.XCross
                    Case enumSymbolType.Plus : .Symbol.Type = ZedGraph.SymbolType.Plus
                    Case enumSymbolType.Star : .Symbol.Type = ZedGraph.SymbolType.Star
                    Case enumSymbolType.Tri_Down : .Symbol.Type = ZedGraph.SymbolType.TriangleDown
                    Case enumSymbolType.HDash : .Symbol.Type = ZedGraph.SymbolType.HDash
                    Case enumSymbolType.VDash : .Symbol.Type = ZedGraph.SymbolType.VDash
                    Case enumSymbolType.Box_Whisker : .Symbol.Type = ZedGraph.SymbolType.None
                End Select
                .Symbol.IsVisible = .Symbol.Type <> ZedGraph.SymbolType.None
                .IsVisible = .Line.IsVisible Or .Symbol.IsVisible Or SymbolType = enumSymbolType.Box_Whisker
                .Label.IsVisible = .IsVisible
            End With
            With ZedCurveMeas
                .Symbol.Type = ZedCurve.Symbol.Type
                .IsVisible = .Symbol.Type <> ZedGraph.SymbolType.None
                .Label.IsVisible = .IsVisible
            End With
            If WasBW Then LineType = _LineType
            If _SymbolType = enumSymbolType.Box_Whisker Or WasBW Then GetData()
        End Set
    End Property

    ''' <summary>
    ''' Set color for this series
    ''' </summary>
    <DisplayName("6. Symbol Border Color"), Category("Line & Symbol Format"), Description("Sets the border color of symbols (same as line color until overridden)")> _
    Public Property SymbolBorderColor() As Color
        Get
            Return ZedCurve.Symbol.Border.Color
        End Get
        Set(ByVal value As Color)
            ZedCurve.Symbol.Border.Color = value
            ZedCurveMeas.Symbol.Border.Color = value
        End Set
    End Property

    ''' <summary>
    ''' Set color for this series
    ''' </summary>
    <DisplayName("7. Symbol Fill Color"), Category("Line & Symbol Format"), Description("Sets the fill color of symbols (same as line color until overridden)")> _
    Public Property SymbolFillColor() As Color
        Get
            Return ZedCurve.Symbol.Fill.Color
        End Get
        Set(ByVal value As Color)
            ZedCurve.Symbol.Fill.Color = value
            ZedCurveMeas.Symbol.Fill.Color = value
        End Set
    End Property

    ''' <summary>
    ''' Sets the symbol size (in points)
    ''' </summary>
    <DisplayName("8. Symbol Size"), Category("Line & Symbol Format"), Description("Sets the symbol size (in points)")> _
    Public Overridable Property SymbolSize() As Single
        Get
            Return ZedCurve.Symbol.Size
        End Get
        Set(ByVal value As Single)
            ZedCurve.Symbol.Size = value
            ZedCurveMeas.Symbol.Size = value
            If _SymbolType = enumSymbolType.Box_Whisker Then GetData()
        End Set
    End Property

    ''' <summary>
    ''' Is the symbol filled?
    ''' </summary>
    <DisplayName("9. Solid Fill?"), Category("Line & Symbol Format"), Description("Is the symbol filled?")> _
    Public Property SymbolFill() As Boolean
        Get
            Return ZedCurve.Symbol.Fill.Type = ZedGraph.FillType.Solid
        End Get
        Set(ByVal value As Boolean)
            ZedCurve.Symbol.Fill.Type = IIf(value, ZedGraph.FillType.Solid, ZedGraph.FillType.None)
            ZedCurveMeas.Symbol.Fill.Type = IIf(value, ZedGraph.FillType.Solid, ZedGraph.FillType.None)
        End Set
    End Property

End Class

Friend Class clsMoreCriteria

    Friend Property MinDate As Date = Date.MinValue
    Friend Property MaxDate As Date = Date.MaxValue
    Friend Property MinDepth As Double = Double.MinValue
    Friend Property MaxDepth As Double = Double.MaxValue
    Friend Property MinWidth As Double = Double.MinValue
    Friend Property MaxWidth As Double = Double.MaxValue
    Friend Property lstCCodes As New Generic.List(Of String)
    Friend Property lstLCodes As New Generic.List(Of String)
    Friend Property lstSCodes As New Generic.List(Of String)
    Friend Property lstQCodes As New Generic.List(Of String)

    Public Overrides Function ToString() As String
        Dim s As String = ""
        If MinDate > New Date(1800, 1, 1) Or MaxDate < New Date(3000, 1, 1) Then s &= IIf(s = "", "", "; ") & String.Format("Dates: {0:d}-{1:d}", MinDate, MaxDate)
        If (MinDepth <> Double.MinValue And MinDepth <> -9999) Or (MaxDepth <> Double.MaxValue And MaxDepth <> 9999) Then s &= IIf(s = "", "", "; ") & String.Format("Depths: {0}-{1}", Math.Max(-9999, MinDepth), Math.Min(9999, MaxDepth))
        If (MinWidth <> Double.MinValue And MinWidth <> -9999) Or (MaxWidth <> Double.MaxValue And MaxWidth <> 9999) Then s &= IIf(s = "", "", "; ") & String.Format("Widths: {0}-{1}", Math.Max(-9999, MinWidth), Math.Min(9999, MaxWidth))
        For i As Integer = 1 To 4
            Dim lst As Generic.List(Of String) = Choose(i, lstCCodes, lstLCodes, lstSCodes, lstQCodes)
            Dim cod As String = Choose(i, "CCodes", "LCodes", "SCodes", "QCodes")
            Dim csv As String = ""
            For Each item As String In lst
                csv &= IIf(csv = "", "", ",") & item
            Next
            If lst.Count > 0 Then s &= IIf(s = "", "", "; ") & String.Format("{0}: {1}", cod, csv)
        Next
        If s = "" Then s = "None"
        Return s
    End Function
End Class

''' <summary>
''' This class was created to display time series information in the properties grid
''' </summary>
Friend Class clsSeriesTimeSeries : Inherits clsSeries

    Friend Sub New(ByRef Zed As ZedGraph.ZedGraphControl)
        MyBase.New(Zed, enumGraphType.TimeSeries)
    End Sub

    Friend Overrides Sub GetDefaultLabel()
        If Table.TableType = clsTable.enumTableType.Calibration AndAlso Not Table.Calibration.AutoSeriesPrefix Then
            Label = Table.Calibration.SeriesPrefix 'will append suffix elsewhere
        Else
            'not a good idea: every time change pcode on one series would have to reset names on ALL series, which would overwrite custom names
            ''look at all series; if all pcodes are identical, don't include in default series name
            'Dim PCode As String = ""
            'Dim AllTheSame As Boolean = True
            'For Each ser As clsSeries In ActiveGraphTab.Series
            '    If PCode = "" Then
            '        PCode = ser.PCode
            '    Else
            '        If ser.PCode <> PCode Then AllTheSame = False : Exit For
            '    End If
            'Next
            'If AllTheSame Then
            '    Label = String.Format("{0}", _StationID)
            'Else
            '    Label = String.Format("{0} at {1}", _PCode, _StationID)
            'End If
            Label = String.Format("{0} at {1}", _PCode, _StationID)
        End If
        AutoLabel = True
    End Sub

    Public Overrides Property Label As String
        Get
            If Table IsNot Nothing AndAlso Table.TableType = clsTable.enumTableType.Calibration Then
                Dim lbl As String = MyBase.Label.Replace(Table.Calibration.LegendSuffixSimulated, "").Trim
                'if have predefined formats and station id matches template, strip suffix
                For Each fmt As clsSeriesFormat In Table.Calibration.SeriesFormats
                    If (String.IsNullOrEmpty(fmt.StationFilter) OrElse StationID Like fmt.StationFilter) AndAlso
                       (String.IsNullOrEmpty(fmt.ParameterFilter) OrElse PCode Like fmt.ParameterFilter) Then
                        If fmt.LegendSuffixSimulated.Trim <> "" Then Return lbl.Replace(fmt.LegendSuffixSimulated, "").Trim
                    End If
                Next
                Return lbl.Trim
            Else
                Return MyBase.Label
            End If
        End Get
        Set(value As String)
            If Table IsNot Nothing AndAlso Table.TableType = clsTable.enumTableType.Calibration Then
                ZedCurve.Label.Text = IIf(value = "", "", value & " ") & Table.Calibration.LegendSuffixSimulated
                ZedCurveMeas.Label.Text = IIf(value = "", "", value & " ") & Table.Calibration.LegendSuffixMeasured

                'if have predefined formats and station id matches template, append suffix
                For Each fmt As clsSeriesFormat In Table.Calibration.SeriesFormats
                    If (String.IsNullOrEmpty(fmt.StationFilter) OrElse StationID Like fmt.StationFilter) AndAlso
                       (String.IsNullOrEmpty(fmt.ParameterFilter) OrElse PCode Like fmt.ParameterFilter) Then
                        If fmt.LegendSuffixSimulated.Trim <> "" And Not ZedCurve.Label.Text.EndsWith(fmt.LegendSuffixSimulated.Trim) Then ZedCurve.Label.Text &= " " & fmt.LegendSuffixSimulated.Trim
                        If fmt.LegendSuffixMeasured.Trim <> "" And Not ZedCurveMeas.Label.Text.EndsWith(fmt.LegendSuffixMeasured.Trim) Then ZedCurveMeas.Label.Text &= " " & fmt.LegendSuffixMeasured.Trim
                        Exit For
                    End If
                Next
            Else
                MyBase.Label = value
            End If
        End Set
    End Property

    Public Overrides Property Table As clsTable
        Get
            Return MyBase.Table
        End Get
        Set(value As clsTable)
            MyBase.Table = value
            'note: to force measured to plot on top of simulated, insert the measured curve before the simulated one
            If value IsNot Nothing AndAlso Table.TableType = clsTable.enumTableType.Calibration Then
                Dim position As Integer = 0
                If Table.Calibration.SeriesOrder = clsCalibration.enumSeriesOrder.MeasOverSim Then position = 0 Else position = 1
                _Zed.GraphPane.CurveList.Insert(position, ZedCurveMeas)
            End If
        End Set
    End Property

    Public Overrides Property SymbolType As clsSeries.enumSymbolType
        Get
            Return MyBase.SymbolType
        End Get
        Set(value As clsSeries.enumSymbolType)
            MyBase.SymbolType = value
            If Table IsNot Nothing AndAlso Table.TableType = clsTable.enumTableType.Calibration Then ZedCurve.Symbol.IsVisible = False 'force symbols to be hidden for simulated
        End Set
    End Property

    ''' <summary>
    ''' Retrieve data into curve given the current station id and pcode; also computes any curve fits that are requested
    ''' </summary>
    Friend Overrides Sub GetData()
        Try
            If IsCalc Then Exit Sub
            MyBase.GetData()

            If _StationID <> "" And _PCode <> "" Then

                Dim ScaleType As clsAxis.enumScaleType = clsAxis.AxisExtra(_Zed.GraphPane.XAxis.Tag).ScaleType

                If Table.TableType = clsTable.enumTableType.Calibration Then
                    With Table.Calibration
                        Dim dtMeas As DataTable = .GetData(_StationID, _PCode, clsCalibration.enumDataType.Measured)
                        Dim dtSim As DataTable = .GetData(_StationID, _PCode, clsCalibration.enumDataType.Simulated)
                        ZedCurve.Clear()

                        If dtSim IsNot Nothing Then
                            For Each r As DataRow In dtSim.Rows
                                Dim dt As Date = r(0)
                                If ScaleType = clsAxis.enumScaleType.Date_Julian Then 'Reset all years to 1900 (formatting will hide it)
                                    dt = New Date(1900, dt.Month, dt.Day, dt.Hour, dt.Minute, dt.Second)
                                End If
                                ZedCurve.AddPoint(dt.ToOADate, r(1))
                            Next
                        End If

                        ZedCurveMeas.Clear()
                        If dtMeas IsNot Nothing Then
                            For Each r As DataRow In dtMeas.Rows
                                Dim dt As Date = r(0)
                                If ScaleType = clsAxis.enumScaleType.Date_Julian Then 'Reset all years to 1900 (formatting will hide it)
                                    dt = New Date(1900, dt.Month, dt.Day, dt.Hour, dt.Minute, dt.Second)
                                End If
                                ZedCurveMeas.AddPoint(dt.ToOADate, r(1))
                            Next
                        End If

                        Dim Stats() As clsCalibration.structStats = Nothing, NumPairs As Integer, ErrorStats As clsCalibration.structErrorStats
                        .CalcStats(dtMeas, dtSim, Stats, NumPairs, ErrorStats)
                        If Stats IsNot Nothing Then
                            _StatsText = String.Format("Station: {0}\nPCode: {1}\nMin Date: {2}\nMax Date: {3}\n" &
                                                       "----{4} Data----\nCount: {5}\nMean Value: {6:N3}\n" &
                                                       "{7} %tile: {8:N3}\n{9} %tile: {10:N3}\n" &
                                                       "----{11} Data----\nCount: {12}\nMean Value: {13:N3}\n" &
                                                       "{14} %tile: {15:N3}\n{16} %tile: {17:N3}",
                                                       _StationID, _PCode, Table.Calibration.MinDate.ToString(MyDateTimeFormat), Table.Calibration.MaxDate.ToString(MyDateTimeFormat),
                                                       Table.Calibration.LegendSuffixSimulated, Stats(1).Count, Stats(1).Mean,
                                                       .PercentileStringLo, .PercentileLo(Stats(1).Percentiles), .PercentileStringHi, .PercentileHi(Stats(1).Percentiles),
                                                       Table.Calibration.LegendSuffixMeasured, Stats(0).Count, Stats(0).Mean,
                                                       .PercentileStringLo, .PercentileLo(Stats(0).Percentiles), .PercentileStringHi, .PercentileHi(Stats(0).Percentiles))
                        End If
                    End With
                Else
                    With ZedCurve
                        .Clear()
                        Dim dtMeas As DataTable
                        With MoreCriteria
                            If MoreCriteria IsNot Nothing AndAlso MoreCriteria.ToString <> "None" Then
                                dtMeas = Table.GetResults(_StationID, _PCode, .MinDate, .MaxDate, .MinDepth, .MaxDepth, .MinWidth, .MaxWidth, .lstCCodes, .lstLCodes, .lstSCodes, .lstQCodes)
                            Else
                                dtMeas = Table.GetResults(_StationID, _PCode)
                            End If
                        End With
                        If dtMeas Is Nothing OrElse dtMeas.Rows.Count = 0 Then Exit Sub
                        'in some circumstances (Paradox), data may not be sorted by date; do so now
                        dtMeas.DefaultView.Sort = "Date_Time"
                        'if plotting julian date, need base year to assign, but make sure is leap year
                        Dim baseyear As Integer = CDate(dtMeas.DefaultView.Item(0)(0)).Year
                        While Not Date.IsLeapYear(baseyear)
                            baseyear -= 1
                        End While
                        Dim sum As Single = 0, min As Single = Single.MaxValue, max As Single = Single.MinValue
                        For r As Integer = 0 To dtMeas.DefaultView.Count - 1
                            Dim dt As Date = dtMeas.DefaultView.Item(r)(0)
                            Dim conc As Double = dtMeas.DefaultView.Item(r)(1)
                            If LineType = enumLineType.Bars And ScaleType <> clsAxis.enumScaleType.Date_Julian Then
                                'assume value is at end-of period, so add bar by drawing to zero at previous time, then up to next value, then over to next value
                                Dim dtPrev As Date
                                If r = 0 Then
                                    Dim deltat As TimeSpan = TimeSpan.Zero
                                    If dtMeas.Rows.Count > 1 Then deltat = dtMeas.DefaultView.Item(1)(0).Subtract(dt) 'get deltat from next time step
                                    dtPrev = dt.Add(-deltat)
                                Else
                                    dtPrev = dtMeas.DefaultView.Item(r - 1)(0)
                                End If
                                ZedCurve.AddPoint(dtPrev.ToOADate, 0)
                                ZedCurve.AddPoint(dtPrev.ToOADate, conc)
                                ZedCurve.AddPoint(dt.ToOADate, conc)
                                ZedCurve.AddPoint(dt.ToOADate, 0.0)
                            Else
                                If ScaleType = clsAxis.enumScaleType.Date_Julian Then 'Reset all years to base year (formatting will hide it)
                                    dt = New Date(baseyear, dt.Month, dt.Day, dt.Hour, dt.Minute, dt.Second)
                                End If
                                ZedCurve.AddPoint(dt.ToOADate, conc)
                            End If
                            sum += conc
                            min = Math.Min(min, conc)
                            max = Math.Max(max, conc)
                        Next
                        Dim mindate, maxdate As Date
                        If .NPts = 0 Then
                            min = 0 : max = 0 : mindate = Now : maxdate = Now
                        Else
                            mindate = Date.FromOADate(.Points(0).X)
                            maxdate = Date.FromOADate(.Points(.NPts - 1).X)
                        End If
                        _StatsText = String.Format("Station: {0}\nPCode: {1}\nCount: {2}\nNon-Calc: {3}\nNon-Det: {4}\nExceed: {5} ({11:N1}%)\n" &
                                                       "Min Date: {6}\nMax Date: {7}\nMin Value: {8:N3}\nMax Value: {9:N3}\nMean Value: {10:N3}",
                                                       _StationID, _PCode, .NPts, Table.NumNonCalc, Table.NumNonDetect, Table.NumExceed,
                                                       mindate.ToString(MyDateTimeFormat), maxdate.ToString(MyDateTimeFormat), min, max, sum / Math.Max(.NPts, 1), Table.NumExceed * 100.0 / Math.Max(.NPts, 1))
                    End With
                End If

            End If
            _NeedsReload = False
        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

End Class

''' <summary>
''' This class was created to display longitudinal profile information in the properties grid
''' </summary>
Friend Class clsSeriesLongProfile : Inherits clsSeries

    Private _ProfXPlot As clsGraphTab.enumProfileXPlot

    Friend Sub New(ByRef Zed As ZedGraph.ZedGraphControl)
        MyBase.New(Zed, enumGraphType.Profile)
        SymbolFill = True
    End Sub

    ''' <summary>
    ''' Indicates the x-axis value that should be used (should be same for all series)
    ''' </summary>
    <DisplayName("2a. Profile Option"), Category("(Data)"), Description("Controls how values are plotted on the X Axis")> _
    Public Property ProfXPlot() As clsGraphTab.enumProfileXPlot
        Get
            Return _ProfXPlot
        End Get
        Set(ByVal value As clsGraphTab.enumProfileXPlot)
            _ProfXPlot = value
            GetData()

            'reset x axis label
            With clsAxis.AxisExtra(_Zed.GraphPane.XAxis.Tag)
                If .AutoTitle Then _Zed.GraphPane.XAxis.Title.Text = IIf(_ProfXPlot = clsGraphTab.enumProfileXPlot.RiverMile, "River Mile", "Segment Number")
            End With
        End Set
    End Property

    Friend Overrides Sub GetDefaultLabel()
        Label = String.Format("{0} at {1}", _PCode, _DateTime.ToString(MyDateTimeFormat))
        AutoLabel = True
    End Sub

    Public Overrides Property Label As String
        Get
            If Table IsNot Nothing AndAlso Table.TableType = clsTable.enumTableType.Calibration Then
                Return MyBase.Label.Replace(Table.Calibration.LegendSuffixSimulated, "").Trim
            Else
                Return MyBase.Label
            End If
        End Get
        Set(value As String)
            If Table IsNot Nothing AndAlso Table.TableType = clsTable.enumTableType.Calibration Then
                ZedCurve.Label.Text = IIf(value = "", "", value & " ") & Table.Calibration.LegendSuffixSimulated
                ZedCurveMeas.Label.Text = IIf(value = "", "", value & " ") & Table.Calibration.LegendSuffixMeasured
            Else
                MyBase.Label = value
            End If
        End Set
    End Property

    ''' <summary>
    ''' Retrieve data into curve given the current pcode; also computes any curve fits that are requested
    ''' </summary>
    ''' <remarks>Remember: _StationID contains the Branch ID</remarks>
    Friend Overrides Sub GetData()
        If IsCalc Then Exit Sub
        MyBase.GetData()
        If _PCode <> "" Then

            ZedCurve.Clear()

            Dim sum As Single = 0, min As Single = Single.MaxValue, max As Single = Single.MinValue, minRMile As Single = Single.MaxValue, maxRMile As Single = Single.MinValue

            Dim mindate, maxdate As Date

            GetMinMaxDate(mindate, maxdate)

            If Table.TableType <> clsTable.enumTableType.Calibration Then 'check to make sure have some river miles
                Dim found As Boolean = False
                For Each kv As KeyValuePair(Of String, clsTable.structStationSupport) In Table.StationSupport
                    If Not Double.IsNaN(kv.Value.RiverMile) Then found = True : Exit For
                Next
                If _ProfXPlot = clsGraphTab.enumProfileXPlot.RiverMile AndAlso Not found Then
                    WarningMsg("The longitudinal profile cannot be plotted against river mile because the data file does not contain the required variable name: {0}; resetting to plot versus segment number.",
                               IIf(Table.TableType = clsTable.enumTableType.BMD,
                                   Table.BMDFieldName(clsTable.enumBMDField.RiverMile),
                                   Table.BMD2FieldName(clsTable.enumBMDField.RiverMile)))
                    ProfXPlot = clsGraphTab.enumProfileXPlot.SegmentNum
                    _Zed.AxisChange()
                    _Zed.Refresh()
                    Exit Sub 'when reset profxplot, it gets the data there so can exit here
                End If
            End If

            If Table.TableType = clsTable.enumTableType.Calibration Then

                If _ProfXPlot = clsGraphTab.enumProfileXPlot.RiverMile Then 'note: will not display non-BMD series if plotversus segment number

                    With Table.Calibration
                        'loop through each station in the branch (stored in _StationID)
                        For Each sta As clsStation In .dictBranches(_StationID).dictStations.Values
                            Dim Station As String = sta.Name
                            Dim RMile As Single = sta.Length
                            Dim dtMeas As DataTable = .GetData(sta.Name, _PCode, clsCalibration.enumDataType.Measured)
                            Dim dtSim As DataTable = .GetData(sta.Name, _PCode, clsCalibration.enumDataType.Simulated)

                            ZedCurve.Clear()

                            If dtSim IsNot Nothing Then
                                For Each r As DataRow In dtSim.Rows
                                    Dim dt As Date = r("Date_Time")
                                    If dt >= mindate And dt <= maxdate Then
                                        Dim y As Double = r("Result")
                                        ZedCurve.AddPoint(RMile, y)
                                        sum += y
                                        min = Math.Min(min, y)
                                        max = Math.Max(max, y)
                                        minRMile = Math.Min(minRMile, RMile)
                                        maxRMile = Math.Max(maxRMile, RMile)
                                    End If
                                Next
                            End If

                            ZedCurveMeas.Clear()

                            'assemble list of all points at each river mile and plot box & whisker series
                            Dim dictData As New Generic.SortedDictionary(Of Double, Generic.List(Of Double))

                            If dtMeas IsNot Nothing Then
                                For Each r As DataRow In dtMeas.Rows
                                    Dim dt As Date = r("Date_Time")
                                    If dt >= mindate And dt <= maxdate Then
                                        Dim y As Double = r("Result")
                                        If SymbolType = enumSymbolType.Box_Whisker Then
                                            If dictData.ContainsKey(RMile) Then
                                                dictData(RMile).Add(y)
                                            Else
                                                dictData.Add(RMile, New Generic.List(Of Double))
                                            End If
                                        Else
                                            ZedCurveMeas.AddPoint(RMile, y)
                                        End If
                                        sum += y
                                        min = Math.Min(min, y)
                                        max = Math.Max(max, y)
                                        minRMile = Math.Min(minRMile, RMile)
                                        maxRMile = Math.Max(maxRMile, RMile)
                                    End If
                                Next
                                If _SymbolType = enumSymbolType.Box_Whisker Then
                                    For Each kv As Generic.KeyValuePair(Of Double, Generic.List(Of Double)) In dictData
                                        If kv.Value.Count > 0 Then
                                            RMile = kv.Key
                                            Dim Median, Q25, Q75, Mn, Mx As Double
                                            CalcStats(kv.Value, Median, Q25, Q75, Mn, Mx)
                                            Dim Width As Double = (_Zed.GraphPane.XAxis.Scale.Max - _Zed.GraphPane.XAxis.Scale.Min) * SymbolSize / 1000
                                            If _LineType <> enumLineType.None Then ZedCurve.AddPoint(RMile, Median)
                                            With ZedCurveMeas
                                                .Line.IsVisible = True
                                                AddBWSymbol(ZedCurve, RMile, Width, Median, Q25, Q75, Mn, Mx)
                                                If _LineType <> enumLineType.None Then ZedCurve.AddPoint(RMile, Median)
                                            End With
                                        End If
                                    Next
                                End If
                            End If
                        Next

                    End With

                End If

                With ZedCurve
                    If .NPts = 0 Then minRMile = 0 : maxRMile = 0 : min = 0 : max = 0
                    _StatsText = String.Format("Branch: {0}\nPCode: {1}\nDate: {2}\nCount: {3}\nNon-Calc: {4}\nNon-Det: {5}\nExceed: {6} ({12:N1}%)\nMin RMile: {7:N2}\nMax RMile: {8:N2}\nMin Value: {9:N3}\nMax Value: {10:N3}\nMean Value: {11:N3}", _StationID, _PCode, _DateTime.ToString(MyDateTimeFormat), .NPts, Table.NumNonCalc, Table.NumNonDetect, Table.NumExceed, minRMile, maxRMile, min, max, sum / Math.Max(.NPts, 1), Table.NumExceed * 100.0 / Math.Max(.NPts, 1))
                End With

            ElseIf Table.TableType = clsTable.enumTableType.BMD Then

                If Not Table.KeepFileOpen Then Table.dsBMD.Reopen()
                Dim arDates() As Date = Table.dsBMD.Times
                If _DateTime >= arDates(0) AndAlso _DateTime <= arDates(arDates.Length - 1) Then
                    Dim y As Single() = Table.dsBMD.Conc(_PCode, _DateTime)
                    Dim dta As New SortedList(Of Single, Single)
                    For s As Integer = 0 To Table.dsBMD.NumSegments - 1
                        Dim Station As String = Table.dsBMD.SegmentName(s)
                        If Table.StationSupport.ContainsKey(Station) Then
                            If _StationID = "All Branches" OrElse (_StationID = Table.StationSupport(Station).BranchID AndAlso Not Double.IsNaN(Table.StationSupport(Station).RiverMile)) Then
                                If y(s) <> Single.NaN Then
                                    dta.Add(IIf(_ProfXPlot = clsGraphTab.enumProfileXPlot.RiverMile, Table.StationSupport(Station).RiverMile, s + 1), y(s))
                                    sum += y(s)
                                    min = Math.Min(min, y(s))
                                    max = Math.Max(max, y(s))
                                    minRMile = Math.Min(minRMile, Table.StationSupport(Station).RiverMile)
                                    maxRMile = Math.Max(maxRMile, Table.StationSupport(Station).RiverMile)
                                End If
                            End If
                        End If
                    Next
                    For Each kv As KeyValuePair(Of Single, Single) In dta
                        ZedCurve.AddPoint(kv.Key, kv.Value) 'will plot in station order
                    Next
                End If
                If Not Table.dsBMD.KeepFileOpen Then Table.dsBMD.Close()

            ElseIf Table.TableType = clsTable.enumTableType.BMD2 Then

                If Not Table.KeepFileOpen Then Table.dsBMD2.Reopen()
                Dim arDates() As Date = Table.dsBMD2.Moments.ToArray
                If _DateTime >= arDates(0) AndAlso _DateTime <= arDates(arDates.Length - 1) Then
                    Dim dta As New SortedList(Of Single, Single)
                    With Table.dsBMD2
                        Dim y As Single(,) = Table.dsBMD2.DataAtTime(Table.dsBMD2.MomentIndex(_DateTime))
                        Dim v As Integer = .VariableIndex(_PCode)
                        For s As Integer = 0 To Table.dsBMD2.NumSegments - 1
                            Dim Station As String = Table.dsBMD2.SegmentName(s)
                            If Table.StationSupport.ContainsKey(Station) Then
                                If _StationID = "All Branches" OrElse (_StationID = Table.StationSupport(Station).BranchID AndAlso Not Double.IsNaN(Table.StationSupport(Station).RiverMile)) Then
                                    If y(v, s) <> Single.NaN Then
                                        dta.Add(IIf(_ProfXPlot = clsGraphTab.enumProfileXPlot.RiverMile, Table.StationSupport(Station).RiverMile, s + 1), y(v, s))
                                        sum += y(v, s)
                                        min = Math.Min(min, y(v, s))
                                        max = Math.Max(max, y(v, s))
                                        minRMile = Math.Min(minRMile, Table.StationSupport(Station).RiverMile)
                                        maxRMile = Math.Max(maxRMile, Table.StationSupport(Station).RiverMile)
                                    End If
                                End If

                                'dta = .DataOverSegment(.VariableIndex(_PCode),
                                '                       .MomentIndex(_DateTime),
                                '                       IIf(_ProfXPlot = clsGraphTab.enumProfileXPlot.RiverMile, Table.BMD2FieldName(clsTable.enumBMDField.RiverMile), ""),
                                '                       IIf(_StationID = "All Branches", "", Table.BMD2FieldName(clsTable.enumBMDField.BranchNum) & "=" & _StationID))
                            End If
                        Next
                    End With
                    For Each kv As KeyValuePair(Of Single, Single) In dta
                        ZedCurve.AddPoint(kv.Key, kv.Value)
                        sum += kv.Value
                        min = Math.Min(min, kv.Value)
                        max = Math.Max(max, kv.Value)
                        minRMile = Math.Min(minRMile, kv.Key)
                        maxRMile = Math.Max(maxRMile, kv.Key)
                    Next

                End If
                If Not Table.KeepFileOpen Then Table.dsBMD2.Close()

            ElseIf Table.TableType = clsTable.enumTableType.WRDBData Then
                'cannot display profile data

            Else
                If _ProfXPlot = clsGraphTab.enumProfileXPlot.RiverMile Then 'note: will not display non-BMD series if plotversus segment number
                    Dim dr As DbDataReader = Table.WRDBProject.DB.ExecuteReader("Select Station_ID,RCode,Result from {0} where PCode='{1}' and Date_Time Between {2} AND {3}{4}", Table.WRDBProject.DB.AddBrackets(Table.TableName), _PCode, Table.WRDBProject.DB.DateTime(mindate), Table.WRDBProject.DB.DateTime(maxdate), Table.SelectedByClause)
                    If dr Is Nothing Then Exit Sub
                    With dr
                        'assemble list of all points at each river mile and plot box & whisker series
                        Dim dictData As New Generic.SortedDictionary(Of Double, Generic.List(Of Double))
                        While .Read
                            Dim Station As String = .GetString(0)
                            If Table.StationSupport.ContainsKey(Station) Then
                                If Not Double.IsNaN(Table.StationSupport(Station).RiverMile) And (_StationID = "All Branches" Or _StationID = Table.StationSupport(Station).BranchID) Then
                                    Dim RMile As Double = Table.StationSupport(Station).RiverMile
                                    Dim y As Double
                                    If Table.GetResult("", _PCode, _DateTime, .GetValue(1), .GetValue(2), y) Then
                                        If SymbolType = enumSymbolType.Box_Whisker Then
                                            If Not dictData.ContainsKey(RMile) Then dictData.Add(RMile, New Generic.List(Of Double))
                                            dictData(RMile).Add(y)
                                        Else
                                            ZedCurve.AddPoint(RMile, y)
                                        End If
                                        sum += y
                                        min = Math.Min(min, y)
                                        max = Math.Max(max, y)
                                        minRMile = Math.Min(minRMile, RMile)
                                        maxRMile = Math.Max(maxRMile, RMile)
                                    End If
                                End If
                            End If
                        End While
                        .Close()
                        dr = Nothing
                        If _SymbolType = enumSymbolType.Box_Whisker Then
                            For Each kv As Generic.KeyValuePair(Of Double, Generic.List(Of Double)) In dictData
                                If kv.Value.Count > 0 Then
                                    Dim RMile As Double = kv.Key
                                    Dim Median, Q25, Q75, Mn, Mx As Double
                                    CalcStats(kv.Value, Median, Q25, Q75, Mn, Mx)
                                    Dim Width As Double = (_Zed.GraphPane.XAxis.Scale.Max - _Zed.GraphPane.XAxis.Scale.Min) * SymbolSize / 1000
                                    If _LineType <> enumLineType.None Then ZedCurve.AddPoint(RMile, Median)
                                    With ZedCurve
                                        .Line.IsVisible = True
                                        AddBWSymbol(ZedCurve, RMile, Width, Median, Q25, Q75, Mn, Mx)
                                        If _LineType <> enumLineType.None Then ZedCurve.AddPoint(RMile, Median)
                                    End With
                                End If
                            Next
                        End If
                    End With
                End If
                With ZedCurve
                    If .NPts = 0 Then minRMile = 0 : maxRMile = 0 : min = 0 : max = 0
                    _StatsText = String.Format("Branch: {0}\nPCode: {1}\nDate: {2}\nCount: {3}\nNon-Calc: {4}\nNon-Det: {5}\nExceed: {6} ({12:N1}%)\nMin RMile: {7:N2}\nMax RMile: {8:N2}\nMin Value: {9:N3}\nMax Value: {10:N3}\nMean Value: {11:N3}", _StationID, _PCode, _DateTime.ToString(MyDateTimeFormat), .NPts, Table.NumNonCalc, Table.NumNonDetect, Table.NumExceed, minRMile, maxRMile, min, max, sum / Math.Max(.NPts, 1), Table.NumExceed * 100.0 / Math.Max(.NPts, 1))
                End With
            End If
        End If
        _NeedsReload = False
    End Sub

    ''' <summary>
    ''' Given a list of numbers, return the statistics needed for box and whisker
    ''' </summary>
    Private Sub CalcStats(ByVal lst As Generic.List(Of Double), ByRef Median As Double, ByRef Q25 As Double, ByRef Q75 As Double, ByRef Min As Double, ByRef Max As Double)
        lst.Sort()

        If lst.Count = 0 Then
            Throw New IndexOutOfRangeException("An empty list was passed to CalcMedian.")
        ElseIf lst.Count Mod 2 = 0 Then 'is even; return avg of center two values
            Median = (lst(lst.Count / 2 - 1) + lst(lst.Count / 2)) / 2.0
        Else
            Median = lst((lst.Count - 1) / 2)
        End If

        Min = lst(0)
        Max = lst(lst.Count - 1)

        If lst.Count = 1 Then
            Q25 = lst(0)
            Q75 = lst(0)
        Else
            If lst.Count Mod 4 = 0 Then 'get exact value in list
                Q25 = lst((lst.Count - 1) / 4)
                Q75 = lst((lst.Count - 1) * 3 / 4)
            Else
                Dim p1, mult As Double, i1, i2 As Integer
                p1 = (lst.Count - 1) / 4.0
                i1 = Math.Floor(p1)
                i2 = Math.Ceiling(p1)
                mult = p1 - i1
                Q25 = lst(i1) + (lst(i2) - lst(i1)) * mult
                p1 = (lst.Count - 1) * 3.0 / 4.0
                i1 = Math.Floor(p1)
                i2 = Math.Ceiling(p1)
                mult = p1 - i1
                Q75 = lst(i1) + (lst(i2) - lst(i1)) * mult
            End If
        End If
    End Sub

    ''' <summary>
    ''' Add Box and Whisker symbol
    ''' </summary>
    Private Sub AddBWSymbol(ByRef crv As ZedGraph.LineItem, ByVal RMile As Double, ByVal Width As Double, ByVal Median As Double, ByVal Q25 As Double, ByVal Q75 As Double, ByVal Min As Double, ByVal Max As Double)
        With crv
            Dim x1 As Double = RMile - Width / 2
            Dim x2 As Double = RMile
            Dim x3 As Double = RMile + Width / 2
            .AddPoint(Double.MaxValue, Double.MaxValue) 'missing point
            .AddPoint(x1, Min)
            .AddPoint(x3, Min)
            .AddPoint(x2, Min)
            .AddPoint(x2, Q25)
            .AddPoint(x1, Q25)
            .AddPoint(x1, Q75)
            .AddPoint(x3, Q75)
            .AddPoint(x3, Q25)
            .AddPoint(x1, Q25)
            .AddPoint(x1, Median)
            .AddPoint(x3, Median)
            .AddPoint(x3, Q75)
            .AddPoint(x2, Q75)
            .AddPoint(x2, Max)
            .AddPoint(x1, Max)
            .AddPoint(x3, Max)
            .AddPoint(Double.MaxValue, Double.MaxValue)
            .AddPoint(x2, Median) 'position here in case want to draw line
            'force to be drawn as solid line and always make visible
            .Line.Style = Drawing2D.DashStyle.Solid
            .Line.IsVisible = True
        End With
    End Sub

    ''' <summary>
    ''' Compute profile curve which is locus of points of average values of constituent at each river mile
    ''' </summary>
    ''' <param name="crv">Newly created curve</param>
    Private Sub ComputeProfile(ByRef crv As ZedGraph.LineItem)
        Try
            With ZedCurve
                Dim colSum As New Generic.SortedDictionary(Of Double, Double)
                Dim colNum As New Generic.SortedDictionary(Of Double, Integer)
                For i As Integer = 0 To .NPts - 1
                    With .Points(i)
                        If Not colSum.ContainsKey(.X) Then
                            colSum.Add(.X, 0)
                            colNum.Add(.X, 0)
                        End If
                        colSum.Item(.X) += .Y
                        colNum.Item(.X) += 1
                    End With
                    Application.DoEvents()
                Next i
                For Each rmile As Double In colSum.Keys
                    crv.AddPoint(rmile, colSum(rmile) / colNum(rmile))
                Next
            End With
        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

End Class

''' <summary>
''' This class was created to display depth profile information in the properties grid
''' </summary>
Friend Class clsSeriesDepthProfile : Inherits clsSeries

    Friend Sub New(ByRef Zed As ZedGraph.ZedGraphControl)
        MyBase.New(Zed, enumGraphType.Depth)
        SymbolFill = True
        SymbolType = enumSymbolType.Circle
        LineType = enumLineType.None
    End Sub

    Friend Overrides Sub GetDefaultLabel()
        Label = String.Format("{0} at {1}", _PCode, _StationID) ', _DateTime.ToString(MyDateTimeFormat))
        AutoLabel = True
    End Sub

    ''' <summary>
    ''' Retrieve data into curve given the current pcode; also computes any curve fits that are requested
    ''' </summary>
    Friend Overrides Sub GetData()
        If IsCalc Then Exit Sub
        If Table.TableType = clsTable.enumTableType.Calibration Or Table.TableType = clsTable.enumTableType.WRDBData Then Exit Sub
        MyBase.GetData()
        If _PCode <> "" Then
            ZedCurve.Clear()

            Dim sum As Single = 0, min As Single = Single.MaxValue, max As Single = Single.MinValue, minDepth As Single = Single.MaxValue, maxDepth As Single = Single.MinValue
            Dim mindate As Date, maxdate As Date

            GetMinMaxDate(mindate, maxdate)

            Dim dta As New SortedList(Of Single, Single)

            Select Case Table.TableType

                Case clsTable.enumTableType.BMD

                    With Table.dsBMD
                        If Not Table.KeepFileOpen Then .Reopen()
                        Dim arDates() As Date = .Times
                        If _DateTime >= arDates(0) AndAlso _DateTime <= arDates(arDates.Length - 1) Then
                            Dim y As Single() = .Conc(_PCode, _DateTime)
                            Dim d As Single() = .Conc(ActiveGraphTab.AnimationInfo.DepthVariable, _DateTime)
                            Dim lstSta As New List(Of String)
                            'find all related segments that are above or below this one
                            'see if segment name is I,J,K type
                            If _StationID Like "I=*J=*K=*" Then 'find all station IDs that have same I and J
                                Dim wildcard As String = _StationID.Substring(0, _StationID.IndexOf("K") + 2) & "*"
                                For Each sta As String In .SegmentNames
                                    If sta Like wildcard And Not lstSta.Contains(sta) Then lstSta.Add(sta)
                                Next
                                lstSta.Sort()
                                lstSta.Reverse() 'sort decreasing values of K (top down)
                                'assign depths and values
                                Dim depth As Single = 0.0
                                For Each sta As String In lstSta
                                    Dim s As String = .SegmentIndex(sta)
                                    Dim thickness As Single = d(s)
                                    depth += thickness / 2.0
                                    dta.Add(depth, y(s))
                                    depth += thickness / 2.0
                                Next
                            End If
                        End If
                        If Not Table.KeepFileOpen Then .Close()
                    End With

                Case clsTable.enumTableType.BMD2

                    With Table.dsBMD2
                        If Not Table.KeepFileOpen Then .Reopen()
                        Dim arDates() As Date = .Moments.ToArray
                        If _DateTime >= arDates(0) AndAlso _DateTime <= arDates(arDates.Length - 1) Then
                            Dim c As Single(,) = .DataAtTime(.MomentIndex(_DateTime))
                            Dim lstSta As New List(Of String)

                            'find all related segments that are above or below this one

                            'see if segment name is I,J,K type

                            If _StationID Like "I=*J=*K=*" Then 'find all station IDs that have same I and J
                                Dim wildcard As String = _StationID.Substring(0, _StationID.IndexOf("K") + 2) & "*"
                                For Each sta As String In .SegmentNames
                                    If sta Like wildcard And Not lstSta.Contains(sta) Then lstSta.Add(sta)
                                Next
                                lstSta.Sort()
                                lstSta.Reverse() 'sort decreasing values of K (top down)
                                'assign depths and values
                                Dim depth As Single = 0.0
                                Dim depthindex As Integer = -1
                                If ActiveGraphTab.AnimationInfo.DepthVariable <> "" Then
                                    depthindex = .VariableIndex(ActiveGraphTab.AnimationInfo.DepthVariable)
                                End If
                                If depthindex = -1 Then
                                    depthindex = .VariableIndex("Depth")
                                End If
                                If depthindex = -1 Then
                                    depthindex = .VariableIndex("Layer Depth")
                                End If
                                For Each sta As String In lstSta
                                    Dim s As String = .SegmentIndex(sta)
                                    Dim thickness As Single = c(depthindex, s)
                                    depth += thickness / 2.0
                                    dta.Add(depth, c(.VariableIndex(_PCode), s))
                                    depth += thickness / 2.0
                                Next

                            ElseIf .SegmentMetaData(.SegmentIndex(_StationID)).ContainsKey("I") And
                                   .SegmentMetaData(.SegmentIndex(_StationID)).ContainsKey("J") And
                                   .SegmentMetaData(.SegmentIndex(_StationID)).ContainsKey("K") Then

                            ElseIf .SegmentMetaData(.SegmentIndex(_StationID)).ContainsKey("SEGABOVE") And
                                   .SegmentMetaData(.SegmentIndex(_StationID)).ContainsKey("SEGBELOW") Then
                            End If
                        End If
                        If Not Table.KeepFileOpen Then .Close()
                    End With

                Case clsTable.enumTableType.WRDBData 'cannot plot
                    Exit Sub
                Case Else
                    Dim dr As DbDataReader = Table.WRDBProject.DB.ExecuteReader("SELECT Depth_M,RCode,Result from {0} WHERE Station_ID='{1}' AND PCode='{2}' AND Depth_M IS NOT NULL AND Date_Time BETWEEN {3} and {4}{5} ORDER BY Depth_M", Table.WRDBProject.DB.AddBrackets(Table.TableName), _StationID, _PCode, Table.WRDBProject.DB.DateTime(mindate), Table.WRDBProject.DB.DateTime(maxdate), Table.SelectedByClause)
                    If dr Is Nothing Then Exit Sub
                    While dr.Read
                        Dim Depth As Double = dr.GetValue(0)
                        Dim Result As Double
                        If Table.GetResult(_StationID, _PCode, _DateTime, dr.GetValue(1), dr.GetValue(2), Result) Then
                            While dta.ContainsKey(Depth)
                                Depth += 0.0000000001 'must be unique, so just add a little bit to make it so
                            End While
                            dta.Add(Depth, Result)
                        End If
                        Application.DoEvents()
                    End While
                    dr.Close()
                    dr = Nothing

            End Select

            For Each kv As KeyValuePair(Of Single, Single) In dta
                ZedCurve.AddPoint(kv.Value, kv.Key)
                sum += kv.Value
                min = Math.Min(min, kv.Value)
                max = Math.Max(max, kv.Value)
                minDepth = Math.Min(minDepth, kv.Key)
                maxDepth = Math.Max(maxDepth, kv.Key)
            Next

            If ZedCurve.NPts = 0 Then minDepth = 0 : maxDepth = 0 : min = 0 : max = 0
            _StatsText = String.Format("Station: {0}\nPCode: {1}\nDate: {2}\nCount: {3}\nNon-Calc: {4}\nNon-Det: {5}\nExceed: {6} ({12:N1}%)\nMin Depth: {7:N2}\nMax Depth: {8:N2}\nMin Value: {9:N3}\nMax Value: {10:N3}\nMean Value: {11:N3}", _StationID, _PCode, _DateTime.ToString(MyDateTimeFormat), ZedCurve.NPts, Table.NumNonCalc, Table.NumNonDetect, Table.NumExceed, minDepth, maxDepth, min, max, sum / Math.Max(ZedCurve.NPts, 1), Table.NumExceed * 100.0 / Math.Max(ZedCurve.NPts, 1))
        End If
        _NeedsReload = False
    End Sub

    ''' <summary>
    ''' Compute profile curve which is locus of points of average values of constituent at each depth
    ''' </summary>
    ''' <param name="crv">Newly created curve</param>
    Private Sub ComputeProfile(ByRef crv As ZedGraph.LineItem)
        Try
            With ZedCurve
                Dim colSum As New Generic.SortedDictionary(Of Double, Double)
                Dim colNum As New Generic.SortedDictionary(Of Double, Integer)
                For i As Integer = 0 To .NPts - 1
                    With .Points(i)
                        If Not colSum.ContainsKey(.Y) Then
                            colSum.Add(.Y, 0)
                            colNum.Add(.Y, 0)
                        End If
                        colSum.Item(.Y) += .X
                        colNum.Item(.Y) += 1
                    End With
                    Application.DoEvents()
                Next i
                For Each depth As Double In colSum.Keys
                    crv.AddPoint(colSum(depth) / colNum(depth), depth)
                Next
            End With
        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

End Class

''' <summary>
''' This class was created to display width profile information in the properties grid
''' </summary>
''' <remarks>Note: only Public properties are visible in the Properties grid</remarks>
<DefaultProperty("StationID")> _
Friend Class clsSeriesWidthProfile : Inherits clsSeries

    Friend Sub New(ByRef Zed As ZedGraph.ZedGraphControl)
        MyBase.New(Zed, enumGraphType.Width)
        SymbolFill = True
    End Sub

    Friend Overrides Sub GetDefaultLabel()
        Label = String.Format("{0} at {1}", _StationID, _DateTime.ToString(MyDateTimeFormat))
        AutoLabel = True
    End Sub

    ''' <summary>
    ''' Only allow "symbols" format
    ''' </summary>
    Public Overrides Property LineType() As enumLineType
        Get
            Return MyBase.LineType
        End Get
        Set(ByVal value As enumLineType)
            If IsCalc Then MyBase.LineType = value Else MyBase.LineType = enumLineType.None
        End Set
    End Property

    ''' <summary>
    ''' Sets the symbol style (do not allow NONE)
    ''' </summary>
    Public Overrides Property SymbolType() As enumSymbolType
        Get
            Return MyBase.SymbolType
        End Get
        Set(ByVal value As enumSymbolType)
            If value = enumSymbolType.None And Not IsCalc Then
                MyBase.SymbolType = enumSymbolType.Circle
            Else
                MyBase.SymbolType = value
            End If
        End Set
    End Property

    ''' <summary>
    ''' Retrieve data into curve given the current pcode
    ''' </summary>
    Friend Overrides Sub GetData()

        If IsCalc Then Exit Sub

        Select Case Table.TableType
            Case clsTable.enumTableType.Calibration, clsTable.enumTableType.WRDBData : Exit Sub
        End Select

        MyBase.GetData()
        If _PCode <> "" Then
            ZedCurve.Clear()

            Dim mindate, maxdate As Date

            GetMinMaxDate(mindate, maxdate)

            Dim sum As Single = 0, min As Single = Single.MaxValue, max As Single = Single.MinValue, minWidth As Single = Single.MaxValue, maxWidth As Single = Single.MinValue

            Dim dta As New SortedList(Of Single, Single)

            Select Case Table.TableType

                'note: can plot BMD/BMD2 width profiles only if initiated from GIS which passes in distances and segment names (distance stored in StationSupport.RiverMile)

                Case clsTable.enumTableType.BMD

                    If Not Table.KeepFileOpen Then Table.dsBMD.Reopen()
                    Dim arDates() As Date = Table.dsBMD.Times
                    If _DateTime >= arDates(0) AndAlso _DateTime <= arDates(arDates.Length - 1) Then
                        Dim y As Single() = Table.dsBMD.Conc(_PCode, _DateTime)
                        For i As Integer = 0 To Table.dsBMD.NumSegments - 1
                            Dim Station As String = Table.dsBMD.SegmentName(i)
                            If Table.StationSupport.ContainsKey(Station) Then
                                Dim rm As Double = Table.StationSupport(Station).RiverMile
                                Dim yy As Double = y(i)
                                If _StationID = "All Branches" OrElse (_StationID = Table.StationSupport(Station).BranchID AndAlso Not Double.IsNaN(rm) AndAlso Not Double.IsNaN(yy)) Then
                                    If Not dta.ContainsKey(rm) Then dta.Add(rm, yy) Else dta(rm) = yy
                                End If
                            End If
                        Next
                    End If
                    If Not Table.dsBMD.KeepFileOpen Then Table.dsBMD.Close()

                Case clsTable.enumTableType.BMD2

                    If Not Table.KeepFileOpen Then Table.dsBMD2.Reopen()
                    Dim arDates() As Date = Table.dsBMD2.Moments.ToArray
                    If _DateTime >= arDates(0) AndAlso _DateTime <= arDates(arDates.Length - 1) Then
                        Dim y As Single(,) = Table.dsBMD2.DataAtTime(Table.dsBMD2.MomentIndex(_DateTime))
                        For i As Integer = 0 To Table.dsBMD2.NumSegments - 1
                            Dim Station As String = Table.dsBMD2.SegmentName(i)
                            If Table.StationSupport.ContainsKey(Station) Then
                                Dim rm As Double = Table.StationSupport(Station).RiverMile
                                Dim yy As Double = y(Table.dsBMD2.VariableIndex(_PCode), i)
                                If Not Double.IsNaN(rm) AndAlso Not Double.IsNaN(yy) Then
                                    If Not dta.ContainsKey(rm) Then dta.Add(rm, yy) Else dta(rm) = yy
                                End If
                            End If
                        Next
                    End If
                    If Not Table.KeepFileOpen Then Table.dsBMD2.Close()

                Case Else
                    Dim dr As DbDataReader = Table.WRDBProject.DB.ExecuteReader("SELECT LEW_Pct,RCode,Result FROM {0} WHERE Station_ID='{1}' AND PCode='{2}' AND LEW_Pct IS NOT NULL AND Date_Time BETWEEN {3} and {4}{5} ORDER BY LEW_Pct", Table.WRDBProject.DB.AddBrackets(Table.TableName), _StationID, _PCode, Table.WRDBProject.DB.DateTime(mindate), Table.WRDBProject.DB.DateTime(maxdate), Table.SelectedByClause)
                    If dr Is Nothing Then Exit Sub
                    While dr.Read
                        Dim Width As Double = dr.GetValue(0)
                        Dim Result As Double = 0.0
                        If Table.GetResult(_StationID, _PCode, _DateTime, dr.GetValue(1), dr.GetValue(2), Result) Then
                            While dta.ContainsKey(Width)
                                Width += 0.0000000001 'keys must be unique so add a little bit
                            End While
                            dta.Add(Width, Result)
                        End If
                    End While
                    dr.Close()
                    dr = Nothing
            End Select

            For Each kv As KeyValuePair(Of Single, Single) In dta
                ZedCurve.AddPoint(kv.Key, kv.Value)
                sum += kv.Value
                min = Math.Min(min, kv.Value)
                max = Math.Max(max, kv.Value)
                minWidth = Math.Min(minWidth, kv.Key)
                maxWidth = Math.Max(maxWidth, kv.Key)
            Next
            If ZedCurve.NPts = 0 Then minWidth = 0 : maxWidth = 0 : min = 0 : max = 0
            _StatsText = String.Format("Station: {0}\nPCode: {1}\nDate: {2}\nCount: {3}\nNon-Calc: {4}\nNon-Det: {5}\nExceed: {6} ({12:N1}%)\nMin Width: {7:N2}\nMax Width: {8:N2}\nMin Value: {9:N3}\nMax Value: {10:N3}\nMean Value: {11:N3}", _StationID, _PCode, _DateTime.ToString(MyDateTimeFormat), ZedCurve.NPts, Table.NumNonCalc, Table.NumNonDetect, Table.NumExceed, minWidth, maxWidth, min, max, sum / Math.Max(ZedCurve.NPts, 1), Table.NumExceed * 100.0 / Math.Max(ZedCurve.NPts, 1))
        End If
        _NeedsReload = False
    End Sub

    ''' <summary>
    ''' Compute profile curve which is locus of points of average values of constituent at each width
    ''' </summary>
    ''' <param name="crv">Newly created curve</param>
    Private Sub ComputeProfile(ByRef crv As ZedGraph.LineItem)
        Try
            With ZedCurve
                Dim colSum As New Generic.SortedDictionary(Of Double, Double)
                Dim colNum As New Generic.SortedDictionary(Of Double, Integer)
                For i As Integer = 0 To .NPts - 1
                    With .Points(i)
                        If Not colSum.ContainsKey(.X) Then
                            colSum.Add(.X, 0)
                            colNum.Add(.X, 0)
                        End If
                        colSum.Item(.X) += .Y
                        colNum.Item(.X) += 1
                    End With
                    Application.DoEvents()
                Next i
                For Each width As Double In colSum.Keys
                    crv.AddPoint(width, colSum(width) / colNum(width))
                Next
            End With
        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

End Class

''' <summary>
''' This class was created to display scatter plot information in the properties grid
''' </summary>
''' <remarks>Note: only Public properties are visible in the Properties grid</remarks>
Friend Class clsSeriesScatter : Inherits clsSeries

    Private _StationIDX, _PCodeX, _PCodeListX As String

    ''' <summary>
    ''' List of valid PCodes associated with series (semi-colon separated)
    ''' </summary>
    Friend Property PCodeListX() As String
        Get
            Return _PCodeListX
        End Get
        Set(ByVal value As String)
            _PCodeListX = value
        End Set
    End Property

    Friend Sub New(ByRef Zed As ZedGraph.ZedGraphControl)
        MyBase.New(Zed, enumGraphType.Scatter)
        LineType = enumLineType.None
        SymbolType = enumSymbolType.Circle
    End Sub

    Friend Overrides Sub GetDefaultLabel()
        If Table.TableType = clsTable.enumTableType.Calibration Then
            Label = String.Format("{0} vs. {1}", Table.Calibration.LegendSuffixSimulated, Table.Calibration.LegendSuffixMeasured)
        Else
            If _StationID = _StationIDX Then
                Label = _StationID
            Else
                Label = String.Format("{0} vs {1}", _StationID, _StationIDX)
            End If
        End If
        AutoLabel = True
    End Sub

    ''' <summary>
    ''' Station ID associated with x axis
    ''' </summary>
    <DisplayName("5a. Station ID (X-Axis)"), Category("(Data)"), Description("Station ID plotted on X-Axis (read-only)"), [ReadOnly](True)> _
    Public Property StationIDX() As String
        Get
            Return _StationIDX
        End Get
        Set(ByVal value As String)
            _StationIDX = value
            _NeedsReload = True
        End Set
    End Property

    ''' <summary>
    ''' PCode associated with x axis
    ''' </summary>
    <DisplayName("6a. PCode (X-Axis)"), Category("(Data)"), Description("PCode plotted on X-Axis (read-only)"), [ReadOnly](True)> _
    Public Property PCodeX() As String
        Get
            Return _PCodeX
        End Get
        Set(ByVal value As String)
            _PCodeX = value
            _NeedsReload = True
        End Set
    End Property

    ''' <summary>
    ''' Station ID associated with y axis
    ''' </summary>
    Friend Property StationIDY() As String
        Get
            Return _StationID
        End Get
        Set(ByVal value As String)
            _StationID = value
            _NeedsReload = True
        End Set
    End Property

    ''' <summary>
    ''' PCode associated with y axis
    ''' </summary>
    Friend Property PCodeY() As String
        Get
            Return _PCode
        End Get
        Set(ByVal value As String)
            _PCode = value
            _NeedsReload = True
        End Set
    End Property

    ''' <summary>
    ''' Only allow "symbols" format
    ''' </summary>
    Public Overrides Property LineType() As enumLineType
        Get
            If IsCalc Then Return MyBase.LineType Else Return enumLineType.None
        End Get
        Set(ByVal value As enumLineType)
            If IsCalc Then MyBase.LineType = value Else MyBase.LineType = enumLineType.None
        End Set
    End Property

    ''' <summary>
    ''' Only allow "symbols" format
    ''' </summary>
    Public Overrides Property SymbolType() As enumSymbolType
        Get
            Return MyBase.SymbolType
        End Get
        Set(ByVal value As enumSymbolType)
            If value = enumSymbolType.None And Not IsCalc Then
                MyBase.SymbolType = enumSymbolType.Circle
            Else
                MyBase.SymbolType = value
            End If
        End Set
    End Property

    ''' <summary>
    ''' Retrieve data into curve given the current station id and pcode; also computes any curve fits that are requested
    ''' </summary>
    Friend Overrides Sub GetData()
        If IsCalc Then Exit Sub
        If Table Is Nothing Then Exit Sub

        MyBase.GetData()

        If _StationIDX <> "" And _PCodeX <> "" And _StationID <> "" And _PCode <> "" Then
            Dim xsum As Single = 0.0, xmin As Double = Double.MaxValue, xmax As Double = Double.MinValue
            Dim ysum As Single = 0.0, ymin As Double = Double.MaxValue, ymax As Double = Double.MinValue
            With ZedCurve
                .Clear()
                Dim DtaY As New Generic.SortedList(Of Date, Double)
                Dim dtX, dtY As DataTable

                If Table.TableType = clsTable.enumTableType.Calibration Then
                    dtY = Table.Calibration.GetData(_StationID, _PCode, clsCalibration.enumDataType.Simulated)
                    dtX = Table.Calibration.GetData(_StationIDX, _PCodeX, clsCalibration.enumDataType.Measured)
                Else
                    dtY = Table.GetResults(_StationID, _PCode)
                    dtX = Table.GetResults(_StationIDX, _PCodeX)
                End If
                For Each r As DataRow In dtY.Rows
                    If Not DtaY.ContainsKey(r(0)) Then DtaY.Add(r(0), r(1))
                    Application.DoEvents()
                Next

                'for each x value, find matching y value (allow interpolation if requested)

                For Each r As DataRow In dtX.Rows
                    Dim tx As Date = r(0)
                    Dim vx As Double = r(1)
                    Dim vy As Double = Double.MinValue 'will replace with closest or interpolated value 

                    If Table.TableType = clsTable.enumTableType.Calibration Then 'interpolate--ignore time window; will not extrapolate
                        Dim tylast As Date = Date.MaxValue
                        For Each ty As Date In DtaY.Keys
                            If ty = tx Then
                                vy = DtaY(ty)
                                Exit For
                            ElseIf ty > tx Then
                                If tylast <> Date.MaxValue Then 'interpolate
                                    vy = DtaY(tylast) + (DtaY(ty) - DtaY(tylast)) * tx.Subtract(tylast).TotalDays / ty.Subtract(tylast).TotalDays
                                End If
                                Exit For
                            Else
                                tylast = ty
                            End If
                        Next

                    Else 'use time window to filter matching values and match single one closest in time
                        Dim txmin As Date = tx.AddMinutes(-TimeWindowMin(TimeWindow))
                        Dim txmax As Date = tx.AddMinutes(TimeWindowMin(TimeWindow))
                        Dim MinDiff As Double = Double.MaxValue

                        For Each ty As Date In DtaY.Keys
                            If ty >= txmin AndAlso ty <= txmax Then
                                Dim diff As Double = Math.Abs(tx.Subtract(ty).TotalDays)
                                If diff < MinDiff Then
                                    MinDiff = diff
                                    vy = DtaY(ty)
                                End If
                            End If
                            If tx > txmax Then Exit For
                        Next
                    End If

                    If vy <> Double.MinValue Then
                        .AddPoint(vx, vy)
                        xmin = Math.Min(xmin, vx)
                        xmax = Math.Max(xmax, vx)
                        ymin = Math.Min(ymin, vy)
                        ymax = Math.Max(ymax, vy)
                        xsum += vx
                        ysum += vy
                    End If

                    Application.DoEvents()
                Next
                If .NPts = 0 Then
                    xmin = 0 : xmax = 0 : ymin = 0 : ymax = 0
                    frmInfoMsg.ShowInfoMsg("Scatter Plot Warning", "No matching points were found for the combination of stations and PCodes you selected ({0}/{1} vs {2}/{3}) and the specified time window ({4} minutes). You may need to reselect your stations and/or PCodes or make the time window larger to allow points to be paired that are not exactly coincident in time.", _StationID, _PCode, _StationIDX, _PCodeX, TimeWindowMin(TimeWindow))
                End If
                _StatsText = String.Format("StationY: {0}\nPCodeY: {1}\nStationX: {9}\nPCodeX: {10}\nCount: {2}\nMin X Value: {3:N3}\nMax X Value: {4:N3}\nMean X Value: {5:N3}\nMin Y Value: {6:N3}\nMax Y Value: {7:N3}\nMean Y Value: {8:N3}", _StationID, _PCode, .NPts, xmin, xmax, xsum / Math.Max(.NPts, 1), ymin, ymax, ysum / Math.Max(.NPts, 1), _StationIDX, _PCodeX, Table.NumExceed * 100.0 / Math.Max(.NPts, 1))
            End With
        End If
        _NeedsReload = False
    End Sub
End Class

''' <summary>
''' This class was created to display probability plot information in the properties grid
''' </summary>
''' <remarks>Note: only Public properties are visible in the Properties grid</remarks>
Friend Class clsSeriesHistogram : Inherits clsSeries

    Private _HistOptions As enumHistOptions = enumHistOptions.Histogram
    Private _NumIntervals As Integer = 8

    Public Enum enumHistOptions
        Histogram
        Cumulative
        Histogram_Cumulative
    End Enum

    Friend Sub New(ByRef Zed As ZedGraph.ZedGraphControl)
        MyBase.New(Zed, enumGraphType.Histogram)
        LineType = enumLineType.Solid
        SymbolType = enumSymbolType.None
        If Table IsNot Nothing AndAlso Table.TableType = clsTable.enumTableType.Calibration Then ZedCurveMeas.Line.Style = Drawing2D.DashStyle.Dash
    End Sub

    Public Overrides Property Label As String
        Get
            If Table IsNot Nothing AndAlso Table.TableType = clsTable.enumTableType.Calibration Then
                Return MyBase.Label.Replace(Table.Calibration.LegendSuffixSimulated, "").Trim
            Else
                Return MyBase.Label
            End If
        End Get
        Set(value As String)
            If Table IsNot Nothing AndAlso Table.TableType = clsTable.enumTableType.Calibration Then
                ZedCurve.Label.Text = IIf(value = "", "", value & " ") & Table.Calibration.LegendSuffixSimulated
                ZedCurveMeas.Label.Text = IIf(value = "", "", value & " ") & Table.Calibration.LegendSuffixMeasured
            Else
                MyBase.Label = value
            End If
        End Set
    End Property

    ''' <summary>
    ''' Probability plot options (not currently used)
    ''' </summary>
    <DisplayName("1. Type of Plot"), Category("Histogram"), Description("Defines the type of histogram plot (read-only)"), [ReadOnly](True)> _
    Public Property HistOption() As enumHistOptions
        Get
            Return _HistOptions
        End Get
        Set(ByVal value As enumHistOptions)
            _HistOptions = value
            _NeedsReload = True
        End Set
    End Property

    ''' <summary>
    ''' Histogram bins
    ''' </summary>
    <DisplayName("2. Number of Intervals"), Category("Histogram"), Description("Defines the number of intervals (bins) the histograph is divided into")> _
    Public Property NumIntervals() As Integer
        Get
            Return _NumIntervals
        End Get
        Set(ByVal value As Integer)
            _NumIntervals = value
            _NeedsReload = True
            GetData()
            ActiveGraphTab.ZoomExtents()
        End Set
    End Property

    ''' <summary>
    ''' Only allow "line" format
    ''' </summary>
    Public Overrides Property LineType() As enumLineType
        Get
            Return enumLineType.None
        End Get
        Set(ByVal value As enumLineType)
            If value = enumLineType.None Then MyBase.LineType = enumLineType.Solid
            If Table IsNot Nothing AndAlso Table.TableType = clsTable.enumTableType.Calibration Then ZedCurveMeas.Line.Style = Drawing2D.DashStyle.Dash
        End Set
    End Property

    ''' <summary>
    ''' Only allow "line" format
    ''' </summary>
    Public Overrides Property SymbolType() As enumSymbolType
        Get
            Return MyBase.SymbolType
        End Get
        Set(ByVal value As enumSymbolType)
            MyBase.SymbolType = enumSymbolType.None
        End Set
    End Property

    ''' <summary>
    ''' Retrieve data into curve given the current station id and pcode; also computes any curve fits that are requested
    ''' </summary>
    Friend Overrides Sub GetData()
        If IsCalc Then Exit Sub
        If Table.TableType = clsTable.enumTableType.Calibration Then Exit Sub
        MyBase.GetData()

        If _StationID <> "" And _PCode <> "" Then
            Dim result As New Generic.List(Of Double)
            Select Case Table.TableType
                Case clsTable.enumTableType.BMD
                    With Table.dsBMD
                        If Not Table.KeepFileOpen Then .Reopen()
                        For Each v As Single In .Conc(.SegmentIndex(_StationID), .VarIndex(_PCode))
                            result.Add(v)
                        Next
                        If Not .KeepFileOpen Then .Close()
                    End With
                Case clsTable.enumTableType.BMD2
                    With Table.dsBMD2
                        If Not Table.KeepFileOpen Then .Reopen()
                        For Each v As Single In .DataOverTime(.SegmentIndex(_StationID), .VariableIndex(_PCode))
                            result.Add(v)
                        Next
                        If Not Table.KeepFileOpen Then .Close()
                    End With
                Case clsTable.enumTableType.WRDBData
                    Exit Sub
                Case Else
                    Dim dr As DbDataReader = Table.WRDBProject.DB.ExecuteReader("Select RCode,Result from {0} where Station_ID='{1}' and PCode='{2}'{3}", Table.WRDBProject.DB.AddBrackets(Table.TableName), _StationID, _PCode, Table.SelectedByClause)
                    If dr Is Nothing Then Exit Sub
                    While dr.Read
                        Dim y As Double
                        If Table.GetResult(_StationID, _PCode, Nothing, dr.GetValue(0), dr.GetValue(1), y) Then
                            result.Add(y)
                        End If
                        Application.DoEvents()
                    End While
                    dr.Close()
            End Select

            Dim sum As Single = 0.0, min As Double = Double.MaxValue, max As Double = Double.MinValue, num As Integer = 0
            For Each y As Double In result
                num += 1
                sum += y
                min = Math.Min(min, y)
                max = Math.Max(max, y)
            Next

            ZedCurve.IsVisible = True
            Dim barwidth As Double = (max - min + 0.000001) / NumIntervals
            Dim Bins(NumIntervals - 1) As Integer

            'count number of values in each bin
            If min <> max Then
                For Each r As Double In Result
                    Dim interval As Integer = Int((r - min) / barwidth)
                    Bins(interval) += 1
                Next
            End If

            'draw bars
            With ZedCurve
                .Clear()
                .Label.Text = MyBase.Label & "-Histogram"
                For i As Integer = 0 To NumIntervals - 1
                    Dim x As Double = min + barwidth * i
                    .AddPoint(x, 0)
                    .AddPoint(x, Bins(i) / num)
                    .AddPoint(x + barwidth, Bins(i) / num)
                    .AddPoint(x + barwidth, 0)
                Next
                If .NPts = 0 Then min = 0 : max = 0
                _StatsText = String.Format("Station: {0}\nPCode: {1}\nCount: {2}\nNon-Calc: {3}\nNon-Det: {4}\nExceed: {5} ({10:N1}%)\nMin Value: {6:N3}\nMax Value: {7:N3}\nMean Value: {8:N3}\nNum Bins: {9}", _StationID, _PCode, .NPts, Table.NumNonCalc, Table.NumNonDetect, Table.NumExceed, min, max, sum / Math.Max(.NPts, 1), NumIntervals, Table.NumExceed * 100.0 / Math.Max(.NPts, 1))
            End With

        End If
        _NeedsReload = False
    End Sub

End Class

''' <summary>
''' This class was created to display probability plot information in the properties grid
''' </summary>
''' <remarks>Note: only Public properties are visible in the Properties grid</remarks>
Friend Class clsSeriesProbability : Inherits clsSeries

    'todo: display 7Q10 on chart

    Private _ProbOptions As enumProbOption

    Friend Sub New(ByRef Zed As ZedGraph.ZedGraphControl)
        MyBase.New(Zed, enumGraphType.Probability)
        LineType = enumLineType.None
        SymbolType = enumSymbolType.Circle
        ProbOption = enumProbOption.LessThan_Incr
    End Sub

    Friend Overrides Sub GetDefaultLabel()
        If Table.TableType = clsTable.enumTableType.Calibration AndAlso Not Table.Calibration.AutoSeriesPrefix Then
            Label = Table.Calibration.SeriesPrefix 'will append suffix elsewhere
        Else
            Label = String.Format("{0} at {1} Plotting Probs", _PCode, _StationID)
        End If
        AutoLabel = True
    End Sub

    Public Overrides Property Label As String
        Get
            If Table IsNot Nothing AndAlso Table.TableType = clsTable.enumTableType.Calibration Then
                Return MyBase.Label.Replace(Table.Calibration.LegendSuffixSimulated, "").Trim
            Else
                Return MyBase.Label
            End If
        End Get
        Set(value As String)
            If Table IsNot Nothing AndAlso Table.TableType = clsTable.enumTableType.Calibration Then
                ZedCurve.Label.Text = IIf(value = "", "", value & " ") & Table.Calibration.LegendSuffixSimulated
                ZedCurveMeas.Label.Text = IIf(value = "", "", value & " ") & Table.Calibration.LegendSuffixMeasured
            Else
                MyBase.Label = value
            End If
        End Set
    End Property

    Public Overrides Property SymbolType As clsSeries.enumSymbolType
        Get
            Return MyBase.SymbolType
        End Get
        Set(value As clsSeries.enumSymbolType)
            MyBase.SymbolType = value
            If Table IsNot Nothing AndAlso Table.TableType = clsTable.enumTableType.Calibration Then ZedCurve.Symbol.IsVisible = False 'force symbols to be hidden for simulated
        End Set
    End Property

    Public Overrides Property Table As clsTable
        Get
            Return MyBase.Table
        End Get
        Set(value As clsTable)
            MyBase.Table = value
            If Table IsNot Nothing AndAlso Table.TableType = clsTable.enumTableType.Calibration Then _Zed.GraphPane.CurveList.Insert(1, ZedCurveMeas)
        End Set
    End Property

    ''' <summary>
    ''' Probability plot options
    ''' </summary>
    <DisplayName("2a. Probability Option"), Category("(Data)"), Description("Controls how probabilities are plotted on the X Axis")> _
    Public Property ProbOption() As enumProbOption
        Get
            Return _ProbOptions
        End Get
        Set(ByVal value As enumProbOption)
            _ProbOptions = value
            Dim s As String = "Greater"
            If _ProbOptions = enumProbOption.LessThan_Decr Or _ProbOptions = enumProbOption.LessThan_Incr Then s = "Less"
            _Zed.GraphPane.XAxis.Title.Text = String.Format("Probability Percent {0} Than", s)
            _Zed.GraphPane.XAxis.Scale.IsReverse = False
            If _ProbOptions = enumProbOption.LessThan_Decr Or _ProbOptions = enumProbOption.GreaterThan_Decr Then _Zed.GraphPane.XAxis.Scale.IsReverse = True
            _NeedsReload = True
            '_CurveFit = enumCurveFit.None
        End Set
    End Property

    'Tim Wool wanted to be able to override default use of symbols only on probability plots, so let following revert to parent class action

    ' ''' <summary>
    ' ''' Only allow "symbols" format
    ' ''' </summary>
    'Public Overrides Property LineType() As enumLineType
    '    Get
    '        Return enumLineType.None
    '    End Get
    '    Set(ByVal value As enumLineType)
    '        If IsCalc Then MyBase.LineType = value Else MyBase.LineType = enumLineType.None
    '    End Set
    'End Property

    ' ''' <summary>
    ' ''' Only allow "symbols" format
    ' ''' </summary>
    'Public Overrides Property SymbolType() As enumSymbolType
    '    Get
    '        Return MyBase.SymbolType
    '    End Get
    '    Set(ByVal value As enumSymbolType)
    '        If IsCalc Then MyBase.SymbolType = value Else MyBase.SymbolType = IIf(value = enumSymbolType.None, enumSymbolType.Square, value)
    '    End Set
    'End Property

    ''' <summary>
    ''' Retrieve data into curve given the current station id and pcode; also computes any curve fits that are requested
    ''' </summary>
    Friend Overrides Sub GetData()
        If IsCalc Then Exit Sub
        MyBase.GetData()

        If _StationID <> "" And _PCode <> "" Then
            Dim min, max, avg As Double

            If Table.TableType = clsTable.enumTableType.Calibration Then
                _StatsText = String.Format("Station: {0}\nPCode: {1}", _StationID, _PCode)
                Dim dtMeas As DataTable = Table.Calibration.GetData(_StationID, _PCode, clsCalibration.enumDataType.Measured)
                Dim dtSim As DataTable = Table.Calibration.GetData(_StationID, _PCode, clsCalibration.enumDataType.Simulated)

                If Table.Calibration.CalcMethod = clsCalibration.enumCalcMethod.PairedData Then
                    Dim dtSimPaired As New DataTable
                    dtSimPaired.Columns.Add("DateTime", GetType(DateTime))
                    dtSimPaired.Columns.Add("Result", GetType(Double))

                    'for each meas value, find matching sim value (allow interpolation if requested)
                    Dim dictSim As New Generic.SortedList(Of Date, Double)
                    For Each r As DataRow In dtSim.Rows
                        If Not dictSim.ContainsKey(r(0)) Then dictSim.Add(r(0), r(1))
                    Next

                    For Each r As DataRow In dtMeas.Rows
                        Dim tMeas As Date = r(0)
                        Dim vSim As Double = Double.MinValue 'will replace with closest or interpolated value 

                        Dim tSimLast As Date = Date.MaxValue
                        For Each tSim As Date In dictSim.Keys
                            If tSim = tMeas Then
                                vSim = dictSim(tSim)
                                Exit For
                            ElseIf tSim > tMeas Then
                                If tSimLast <> Date.MaxValue Then 'interpolate
                                    vSim = dictSim(tSimLast) + (dictSim(tSim) - dictSim(tSimLast)) * tMeas.Subtract(tSimLast).TotalDays / tSim.Subtract(tSimLast).TotalDays
                                End If
                                Exit For
                            Else
                                tSimLast = tSim
                            End If
                        Next

                        If vSim <> Double.MinValue Then
                            dtSimPaired.Rows.Add(tMeas, vSim)
                        End If
                    Next
                    AddPoints(dtSimPaired, ZedCurve, min, max, avg)
                Else
                    AddPoints(dtSim, ZedCurve, min, max, avg)
                End If

                With Table.Calibration.tblSimulated
                    'note: make sure these stats are correct if paired data
                    _StatsText &= String.Format("\n*{0} Values*\nCount: {1}\nNon-Calc: {2}\nNon-Det: {3}\nExceed: {4} ({8:N1}%)\nMin Value: {5:N3}\nMax Value: {6:N3}\nMean Value: {7:N3}",
                                                Table.Calibration.LegendSuffixSimulated, ZedCurve.NPts, .NumNonCalc, .NumNonDetect, .NumExceed, min, max, avg, .NumExceed * 100.0 / Math.Max(ZedCurve.NPts, 1))
                End With

                AddPoints(dtMeas, ZedCurveMeas, min, max, avg)
                With Table.Calibration.tblMeasured
                    _StatsText &= String.Format("\n*{0} Values*\nCount: {1}\nNon-Calc: {2}\nNon-Det: {3}\nExceed: {4} ({8:N1}%)\nMin Value: {5:N3}\nMax Value: {6:N3}\nMean Value: {7:N3}",
                                                Table.Calibration.LegendSuffixMeasured, ZedCurveMeas.NPts, .NumNonCalc, .NumNonDetect, .NumExceed, min, max, avg, .NumExceed * 100.0 / Math.Max(ZedCurveMeas.NPts, 1))
                End With
            Else
                AddPoints(Table.GetResults(_StationID, _PCode), ZedCurve, min, max, avg)
                _StatsText = String.Format("Station: {0}\nPCode: {1}\nCount: {2}\nNon-Calc: {3}\nNon-Det: {4}\nExceed: {5} ({9:N1}%)\nMin Value: {6:N3}\nMax Value: {7:N3}\nMean Value: {8:N3}",
                                           _StationID, _PCode, ZedCurve.NPts, Table.NumNonCalc, Table.NumNonDetect, Table.NumExceed, min, max, avg, Table.NumExceed * 100.0 / Math.Max(ZedCurve.NPts, 1))
            End If
        End If
        _NeedsReload = False
    End Sub

    Private Sub AddPoints(dt As DataTable, zc As ZedGraph.LineItem, ByRef min As Double, ByRef max As Double, ByRef avg As Double)
        zc.Clear()
        dt.DefaultView.Sort = "Result"
        Dim sum As Single = 0
        min = Single.MaxValue
        max = Single.MinValue
        For r As Integer = 0 To dt.Rows.Count - 1
            Dim y As Double = dt.DefaultView(r)(1)
            sum += y
            min = Math.Min(min, y)
            max = Math.Max(max, y)
            Dim Prob As Double = (r + 1.0) / (dt.Rows.Count + 1)
            If _ProbOptions = enumProbOption.GreaterThan_Decr Or _ProbOptions = enumProbOption.GreaterThan_Incr Then Prob = 1 - Prob
            zc.AddPoint(Prob, y)
            Application.DoEvents()
        Next
        If zc.NPts = 0 Then min = 0 : max = 0 : avg = 0 Else avg = sum / zc.NPts
    End Sub

End Class

''' <summary>
''' This class was created to display flow/load-duration plot information in the properties grid
''' </summary>
''' <remarks>Note: only Public properties are visible in the Properties grid</remarks>
Friend Class clsSeriesFlowDuration : Inherits clsSeries

    Friend Sub New(ByRef Zed As ZedGraph.ZedGraphControl)
        MyBase.New(Zed, enumGraphType.FlowDuration)
        LineType = enumLineType.Solid
        SymbolType = enumSymbolType.None
    End Sub

    ''' <summary>
    ''' Retrieve data into curve given the current station id and pcode; also computes any curve fits that are requested
    ''' </summary>
    Friend Overrides Sub GetData()
        If IsCalc Then Exit Sub
        If Table.TableType = clsTable.enumTableType.Calibration Then Exit Sub
        MyBase.GetData()

        If _StationID <> "" And _PCode <> "" Then
            With ZedCurve
                .Clear()
                Dim Result As New Generic.List(Of Double)
                Select Case Table.TableType
                    Case clsTable.enumTableType.BMD
                        With Table.dsBMD
                            If Not Table.KeepFileOpen Then .Reopen()
                            For Each v As Single In .Conc(.SegmentIndex(_StationID), .VarIndex(_PCode))
                                Result.Add(v)
                            Next
                            If Not .KeepFileOpen Then .Close()
                        End With
                    Case clsTable.enumTableType.BMD2
                        With Table.dsBMD2
                            If Not Table.KeepFileOpen Then .Reopen()
                            For Each v As Single In .DataOverTime(.SegmentIndex(_StationID), .VariableIndex(_PCode))
                                Result.Add(v)
                            Next
                            If Not Table.KeepFileOpen Then .Close()
                        End With
                    Case clsTable.enumTableType.WRDBData
                        Exit Sub
                    Case Else
                        If Not Table.WRDBProject.DB.TableExists(Table.TableName) And Not Table.WRDBProject.DB.ViewExists(Table.TableName) Then Exit Sub
                        Dim dr As DbDataReader = Table.WRDBProject.DB.ExecuteReader("SELECT RCode,Result FROM {0} WHERE Station_ID='{1}' AND PCode='{2}'{3}", Table.WRDBProject.DB.AddBrackets(Table.TableName), _StationID, _PCode, Table.SelectedByClause)
                        If dr Is Nothing Then Exit Sub
                        While dr.Read
                            Dim y As Double
                            If Table.GetResult(_StationID, _PCode, Nothing, dr.GetValue(0), dr.GetValue(1), y) Then
                                Result.Add(y)
                            End If
                            Application.DoEvents()
                        End While
                        dr.Close()
                        dr = Nothing
                End Select

                Dim sum As Single = 0, min As Single = Single.MaxValue, max As Single = Single.MinValue

                Result.Sort()
                For i As Integer = 0 To Result.Count - 1
                    Dim Pct As Double = Math.Round((100.0 - CDbl(i) / (Result.Count - 1) * 100.0), 3)
                    Dim y As Double = Result(i)
                    .AddPoint(Pct, y)
                    sum += y
                    min = Math.Min(min, y)
                    max = Math.Max(max, y)
                    Application.DoEvents()
                Next

                If .NPts = 0 Then min = 0 : max = 0
                _StatsText = String.Format("Station: {0}\nPCode: {1}\nCount: {2}\nNon-Calc: {3}\nNon-Det: {4}\nExceed: {5} ({9:N1}%)\nMin Value: {6:N3}\nMax Value: {7:N3}\nMean Value: {8:N3}", _StationID, _PCode, .NPts, Table.NumNonCalc, Table.NumNonDetect, Table.NumExceed, min, max, sum / Math.Max(.NPts, 1), Table.NumExceed * 100.0 / Math.Max(.NPts, 1))
            End With
            MyBase.Label = String.Format("{0} at {1} Load-Duration", _PCode, _StationID)
        End If
        _NeedsReload = False
    End Sub

End Class

